TT#27550 implement interface round-robin selection

Change-Id: Id5cf290cc9d044716b5f55cf416dc40b87f23f24
changes/14/17914/3
Richard Fuchs 8 years ago
parent 61d828a48f
commit 1aa9944fe4

@ -228,46 +228,7 @@ The options are described in more detail below.
* -i, --interface
Specifies a local network interface for RTP. At least one must be given, but multiple can be specified.
The format of the value is `[NAME/]IP[!IP]` with `IP` being either an IPv4 address or an IPv6 address.
The second IP address after the exclamation point is optional and can be used if the address to advertise
in outgoing SDP bodies should be different from the actual local address. This can be useful in certain
cases, such as your SIP proxy being behind NAT. For example, `--interface=10.65.76.2!192.0.2.4` means
that 10.65.76.2 is the actual local address on the server, but outgoing SDP bodies should advertise
192.0.2.4 as the address that endpoints should talk to. Note that you may have to escape the exlamation
point from your shell, e.g. using `\!`.
Giving an interface a name (separated from the address by a slash) is optional; if omitted, the name
`default` is used. Names are useful to create logical interfaces which consist of one or more local
addresses. It is then possible to instruct *rtpengine* to use particular interfaces when processing
an SDP message, to use different local addresses when talking to different endpoints. The most common use
case for this is to bridge between one or more private IP networks and the public internet.
For example, if clients coming from a private IP network must communicate their RTP with the local
address 10.35.2.75, while clients coming from the public internet must communicate with your other
local address 192.0.2.67, you could create one logical interface `pub` and a second one `priv` by
using `--interface=pub/192.0.2.67 --interface=priv/10.35.2.75`. You can then use the `direction`
option to tell *rtpengine* which local address to use for which endpoints (either `pub` or `priv`).
If multiple logical interfaces are configured, but the `direction` option isn't given in a
particular call, then the first interface given on the command line will be used.
It is possible to specify multiple addresses for the same logical interface (the same name). Most
commonly this would be one IPv4 addrsess and one IPv6 address, for example:
`--interface=192.168.63.1 --interface=fe80::800:27ff:fe00:0`. In this example, no interface name
is given, therefore both addresses will be added to a logical interface named `default`. You would use
the `address family` option to tell *rtpengine* which address to use in a particular case.
It is also possible to have multiple addresses of the same family in a logical network interface. In
this case, the first address (of a particular family) given for an interface will be the primary address
used by *rtpengine* for most purposes. Any additional addresses will be advertised as additional ICE
candidates with increasingly lower priority. This is useful on multi-homed systems and allows endpoints
to choose the best possible path to reach the RTP proxy. If ICE is not being used, then additional
addresses will go unused.
If you're not using the NG protocol but rather the legacy UDP protocol used by the *rtpproxy* module,
the interfaces must be named `internal` and `external` corresponding to the `i` and `e` flags if you
wish to use network bridging in this mode.
See the section *Interfaces configuration* just below for details.
* -l, --listen-tcp, -u, --listen-udp, -n, --listen-ng
@ -591,6 +552,82 @@ A typical command line (enabling both UDP and NG protocols) thus may look like:
--listen-udp=127.0.0.1:22222 --listen-ng=127.0.0.1:2223 --tos=184 \
--pidfile=/var/run/rtpengine.pid
Interfaces configuration
------------------------
The command-line options `-i` or `--interface=`, or equivalently the `interface=` config file option,
specifie a local network interfaces for RTP. At least one must be given, but multiple can be specified.
The format of the value is `[NAME/]IP[!IP]` with `IP` being either an IPv4 address or an IPv6 address.
To configure multiple interfaces using the command-line options, simply present multiple `-i` or
`--interface=` options. When using the config file, only use a single `interface=` line, but specify
multiple values separated by semicolons (e.g. `interface = internal/12.23.34.45;external/23.34.45.54`).
The second IP address after the exclamation point is optional and can be used if the address to advertise
in outgoing SDP bodies should be different from the actual local address. This can be useful in certain
cases, such as your SIP proxy being behind NAT. For example, `--interface=10.65.76.2!192.0.2.4` means
that 10.65.76.2 is the actual local address on the server, but outgoing SDP bodies should advertise
192.0.2.4 as the address that endpoints should talk to. Note that you may have to escape the exlamation
point from your shell when using command-line options, e.g. using `\!`.
Giving an interface a name (separated from the address by a slash) is optional; if omitted, the name
`default` is used. Names are useful to create logical interfaces which consist of one or more local
addresses. It is then possible to instruct *rtpengine* to use particular interfaces when processing
an SDP message, to use different local addresses when talking to different endpoints. The most common use
case for this is to bridge between one or more private IP networks and the public internet.
For example, if clients coming from a private IP network must communicate their RTP with the local
address 10.35.2.75, while clients coming from the public internet must communicate with your other
local address 192.0.2.67, you could create one logical interface `pub` and a second one `priv` by
using `--interface=pub/192.0.2.67 --interface=priv/10.35.2.75`. You can then use the `direction`
option to tell *rtpengine* which local address to use for which endpoints (either `pub` or `priv`).
If multiple logical interfaces are configured, but the `direction` option isn't given in a
particular call, then the first interface given on the command line will be used.
It is possible to specify multiple addresses for the same logical interface (the same name). Most
commonly this would be one IPv4 addrsess and one IPv6 address, for example:
`--interface=192.168.63.1 --interface=fe80::800:27ff:fe00:0`. In this example, no interface name
is given, therefore both addresses will be added to a logical interface named `default`. You would use
the `address family` option to tell *rtpengine* which address to use in a particular case.
It is also possible to have multiple addresses of the same family in a logical network interface. In
this case, the first address (of a particular family) given for an interface will be the primary address
used by *rtpengine* for most purposes. Any additional addresses will be advertised as additional ICE
candidates with increasingly lower priority. This is useful on multi-homed systems and allows endpoints
to choose the best possible path to reach the RTP proxy. If ICE is not being used, then additional
addresses will go unused, even though ports would still get allocated on those interfaces.
Another option is to give interface names in the format `BASE:SUFFIX`. This allows interfaces to be
used in a round-robin fashion, useful for load-balancing the port ranges of multiple interfaces.
For example, consider the following configuration:
`--interface=pub:1/192.0.2.67 --interface=pub:2/10.35.2.75`. These two interfaces can still be
referenced directly by name (e.g. `direction=pub:1`), but it is now also possible to reference only
the base name (i.e. `direction=pub`). If the base name is used, one of the two interfaces is selected
in a round-robin fashion, and only if the interface actually has enough open ports available. This
makes it possible to effectively increase the number of available media ports across multiple IP
addresses. There is no limit on how many interfaces can share the same base name.
It is possible to combine the `BASE:SUFFIX` notation with specifying multiple addresses for the same
interface name. An advanced example could be (using config file notation, and omitting actual
network addresses):
```
interface = pub:1/IPv4 pub:1/IPv4 pub:1/IPv6 pub:2/IPv4 pub:2/IPv6 pub:3/IPv6 pub:4/IPv4
```
In this example, when `direction=pub` is IPv4 is needed as a primary address, either `pub:1`, `pub:2`,
or `pub:4` might be selected. When `pub:1` is selected, one IPv4 and one IPv6 address will be used
as additional ICE alternatives. For `pub:2`, only one IPv6 is used as ICE alternative, and for `pub:4`
no alternatives would be used. When IPv6 is needed as a primary address, either `pub:1`, `pub:2`, or
`pub:3` might be selected. If at any given time not enough ports are available on any interface,
it will not be selected by the round-robin algorithm.
If you're not using the NG protocol but rather the legacy UDP protocol used by the *rtpproxy* module,
the interfaces must be named `internal` and `external` corresponding to the `i` and `e` flags if you
wish to use network bridging in this mode.
In-kernel Packet Forwarding
---------------------------

@ -60,9 +60,6 @@ G_STATIC_ASSERT (sizeof *(atomic) == sizeof (gint)); \
#define NUM_THREAD_BUFS 8
#define ALGORITHM_DEFAULT ""
#define ALGORITHM_ROUND_ROBIN_CALLS "round-robin-calls"
/*** GLOBALS ***/
extern __thread struct timeval g_now;

@ -1381,7 +1381,9 @@ static void __init_interface(struct call_media *media, const str *ifname, int nu
goto get;
if (!ifname || !ifname->s)
return;
if (!str_cmp_str(&media->logical_intf->name, ifname) || !str_cmp(ifname, ALGORITHM_ROUND_ROBIN_CALLS))
if (!str_cmp_str(&media->logical_intf->name, ifname))
return;
if (g_hash_table_lookup(media->logical_intf->rr_specs, ifname))
return;
get:
media->logical_intf = get_logical_interface(ifname, media->desired_family, num_ports);
@ -1471,33 +1473,6 @@ static void __ice_start(struct call_media *media) {
ice_agent_init(&media->ice_agent, media);
}
static int get_algorithm_num_ports(GQueue *streams, char *algorithm) {
unsigned int algorithm_ports = 0;
struct stream_params *sp;
GList *media_iter;
if (algorithm == NULL) {
return 0;
}
for (media_iter = streams->head; media_iter; media_iter = media_iter->next) {
sp = media_iter->data;
if (!str_cmp(&sp->direction[0], algorithm)) {
algorithm_ports += sp->consecutive_ports;
}
if (!str_cmp(&sp->direction[1], algorithm)) {
algorithm_ports += sp->consecutive_ports;
}
}
// XXX only do *=2 for RTP streams?
algorithm_ports *= 2;
return algorithm_ports;
}
static void __endpoint_loop_protect(struct stream_params *sp, struct call_media *media) {
struct intf_address intf_addr;
@ -1529,7 +1504,6 @@ int monologue_offer_answer(struct call_monologue *other_ml, GQueue *streams,
GList *media_iter, *ml_media, *other_ml_media;
struct call_media *media, *other_media;
unsigned int num_ports;
unsigned int rr_calls_ports;
struct call_monologue *monologue;
struct endpoint_map *em;
struct call *call;
@ -1547,9 +1521,6 @@ int monologue_offer_answer(struct call_monologue *other_ml, GQueue *streams,
call->last_signal = poller_now;
call->deleted = 0;
// get the total number of ports needed for ALGORITHM_ROUND_ROBIN_CALLS algorithm
rr_calls_ports = get_algorithm_num_ports(streams, ALGORITHM_ROUND_ROBIN_CALLS);
__C_DBG("this="STR_FORMAT" other="STR_FORMAT, STR_FMT(&monologue->tag), STR_FMT(&other_ml->tag));
__tos_change(call, flags);
@ -1559,7 +1530,6 @@ int monologue_offer_answer(struct call_monologue *other_ml, GQueue *streams,
for (media_iter = streams->head; media_iter; media_iter = media_iter->next) {
sp = media_iter->data;
__C_DBG("processing media stream #%u", sp->index);
__C_DBG("free ports needed for round-robin-calls, left for this call %u", rr_calls_ports);
/* first, check for existence of call_media struct on both sides of
* the dialogue */
@ -1639,9 +1609,15 @@ int monologue_offer_answer(struct call_monologue *other_ml, GQueue *streams,
media->desired_family = sp->desired_family;
}
/* determine number of consecutive ports needed locally.
* XXX only do *=2 for RTP streams? */
num_ports = sp->consecutive_ports;
num_ports *= 2;
/* local interface selection */
__init_interface(media, &sp->direction[1], rr_calls_ports);
__init_interface(other_media, &sp->direction[0], rr_calls_ports);
__init_interface(media, &sp->direction[1], num_ports);
__init_interface(other_media, &sp->direction[0], num_ports);
if (media->logical_intf == NULL || other_media->logical_intf == NULL) {
goto error_intf;
@ -1658,12 +1634,6 @@ int monologue_offer_answer(struct call_monologue *other_ml, GQueue *streams,
MEDIA_SET(other_media, INITIALIZED);
/* determine number of consecutive ports needed locally.
* XXX only do *=2 for RTP streams? */
num_ports = sp->consecutive_ports;
num_ports *= 2;
if (!sp->rtp_endpoint.port) {
/* Zero port: stream has been rejected.
* RFC 3264, chapter 6:
@ -1686,11 +1656,6 @@ int monologue_offer_answer(struct call_monologue *other_ml, GQueue *streams,
em = __get_endpoint_map(media, num_ports, &sp->rtp_endpoint, flags);
if (!em) {
goto error_ports;
} else {
// update the ports needed for ALGORITHM_ROUND_ROBIN_CALLS algorithm
if (str_cmp(&sp->direction[1], ALGORITHM_ROUND_ROBIN_CALLS) == 0) {
rr_calls_ports -= num_ports;
}
}
__num_media_streams(media, num_ports);
@ -1702,11 +1667,6 @@ int monologue_offer_answer(struct call_monologue *other_ml, GQueue *streams,
* when the answer comes. */
if (__wildcard_endpoint_map(other_media, num_ports))
goto error_ports;
// update the ports needed for ALGORITHM_ROUND_ROBIN_CALLS algorithm
if (str_cmp(&sp->direction[0], ALGORITHM_ROUND_ROBIN_CALLS) == 0) {
rr_calls_ports -= num_ports;
}
}
init:

@ -196,6 +196,10 @@ static struct intf_config *if_addr_parse(char *s) {
ifa->port_min = port_min;
ifa->port_max = port_max;
// handle "base:suffix" separation for round-robin selection
ifa->name_rr_spec = ifa->name;
str_token(&ifa->name_base, &ifa->name_rr_spec, ':'); // sets name_rr_spec to null string if no ':' found
return ifa;
}

@ -48,6 +48,12 @@ struct streamhandler {
const struct streamhandler_io *in;
const struct streamhandler_io *out;
};
struct intf_rr {
struct logical_intf hash_key;
mutex_t lock;
GQueue logical_intfs;
struct logical_intf *singular; // set iff only one is present in the list - no lock needed
};
static void determine_handler(struct packet_stream *in, const struct packet_stream *out);
@ -74,6 +80,9 @@ static int call_savpf2avp_rtcp(str *s, struct packet_stream *, struct stream_fd
//static int call_savpf2savp_rtcp(str *s, struct packet_stream *);
static struct logical_intf *__get_logical_interface(const str *name, sockfamily_t *fam);
@ -256,13 +265,12 @@ static const struct rtpengine_srtp __res_null = {
static GQueue *__interface_list_for_family(sockfamily_t *fam);
static GHashTable *__logical_intf_name_family_hash;
static GHashTable *__intf_spec_addr_type_hash;
static GHashTable *__local_intf_addr_type_hash; // hash of lists
static GHashTable *__logical_intf_name_family_hash; // name + family -> struct logical_intf
static GHashTable *__logical_intf_name_family_rr_hash; // name + family -> struct intf_rr
static GHashTable *__intf_spec_addr_type_hash; // addr + type -> struct intf_spec
static GHashTable *__local_intf_addr_type_hash; // addr + type -> GList of struct local_intf
static GQueue __preferred_lists_for_family[__SF_LAST];
static __thread unsigned int selection_index = 0;
static __thread unsigned int selection_count = 0;
/* checks for free no_ports on a local interface */
@ -272,7 +280,7 @@ static int has_free_ports_loc(struct local_intf *loc, unsigned int num_ports) {
return 0;
}
if (num_ports > loc->spec->port_pool.free_ports) {
if (num_ports > g_atomic_int_get(&loc->spec->port_pool.free_ports)) {
ilog(LOG_ERR, "Didn't found %d ports available for %.*s/%s",
num_ports, loc->logical->name.len, loc->logical->name.s,
sockaddr_print_buf(&loc->spec->local_address.addr));
@ -332,64 +340,49 @@ static int has_free_ports_log_all(struct logical_intf *log, unsigned int num_por
}
/* run round-robin-calls algorithm */
static struct logical_intf* run_round_robin_calls(GQueue *q, unsigned int num_ports) {
static struct logical_intf* run_round_robin_calls(struct intf_rr *rr, unsigned int num_ports) {
struct logical_intf *log = NULL;
volatile unsigned int nr_tries = 0;
unsigned int nr_logs = 0;
nr_logs = g_queue_get_length(q);
mutex_lock(&rr->lock);
select_log:
// choose the next logical interface
log = g_queue_peek_nth(q, selection_index);
if (!log) {
if (selection_index == 0)
return NULL;
selection_index = 0;
goto select_log;
}
__C_DBG("Trying %d ports on logical interface %.*s", num_ports, log->name.len, log->name.s);
unsigned int max_tries = rr->logical_intfs.length;
unsigned int num_tries = 0;
// test for free ports for the logical interface
if(!has_free_ports_log_all(log, num_ports)) {
// count the logical interfaces tried
nr_tries++;
while (num_tries++ < max_tries) {
log = g_queue_pop_head(&rr->logical_intfs);
g_queue_push_tail(&rr->logical_intfs, log);
// the logical interface selected has no ports available, try another one
selection_index ++;
selection_index = selection_index % nr_logs;
mutex_unlock(&rr->lock);
// all the logical interfaces have no ports available
if (nr_tries == nr_logs) {
ilog(LOG_ERR, "No logical interface with free ports found; fallback to default behaviour");
return NULL;
}
__C_DBG("Trying %d ports on logical interface " STR_FORMAT, num_ports, STR_FMT(&log->name));
if (has_free_ports_log_all(log, num_ports))
goto done;
log = NULL;
goto select_log;
mutex_lock(&rr->lock);
}
__C_DBG("Round Robin Calls algorithm found logical %.*s; count=%u index=%u", log->name.len, log->name.s, selection_count, selection_index);
mutex_unlock(&rr->lock);
// 1 stream => 2 x get_logical_interface calls at offer
// 2 streams => 4 x get_logical_interface calls at offer
selection_count ++;
if (selection_count % (num_ports / 2) == 0) {
selection_count = 0;
selection_index ++;
selection_index = selection_index % nr_logs;
done:
if (!log) {
ilog(LOG_ERR, "No logical interface with free ports found; fallback to default behaviour");
return NULL;
}
__C_DBG("Round Robin Calls algorithm found logical " STR_FORMAT, STR_FMT(&log->name));
return log;
}
// 'fam' may only be NULL if 'name' is also NULL
struct logical_intf *get_logical_interface(const str *name, sockfamily_t *fam, int num_ports) {
struct logical_intf d, *log = NULL;
struct logical_intf *log = NULL;
__C_DBG("Get logical interface for %d ports", num_ports);
if (G_UNLIKELY(!name || !name->s ||
str_cmp(name, ALGORITHM_ROUND_ROBIN_CALLS) == 0)) {
if (G_UNLIKELY(!name || !name->s)) {
// trivial case: no interface given. just pick one suitable for the address family.
// always used for legacy TCP and UDP protocols.
GQueue *q;
if (fam)
q = __interface_list_for_family(fam);
@ -404,35 +397,46 @@ got_some:
;
}
// round-robin-calls behaviour - return next interface with free ports
if (name && name->s && str_cmp(name, ALGORITHM_ROUND_ROBIN_CALLS) == 0 && num_ports > 0) {
log = run_round_robin_calls(q, num_ports);
if (log) {
__C_DBG("Choose logical interface %.*s because of direction %.*s",
log->name.len, log->name.s,
name->len, name->s);
} else {
__C_DBG("Choose logical interface NULL because of direction %.*s",
name->len, name->s);
}
return log;
}
// default behaviour - return first logical interface
return q->head ? q->head->data : NULL;
}
// check if round-robin is desired
struct logical_intf key;
key.name = *name;
key.preferred_family = fam;
struct intf_rr *rr = g_hash_table_lookup(__logical_intf_name_family_rr_hash, &key);
if (!rr)
return __get_logical_interface(name, fam);
if (rr->singular) {
__C_DBG("Returning non-RR logical interface '" STR_FORMAT "' based on direction '" \
STR_FORMAT "'",
STR_FMT(&rr->singular->name),
STR_FMT(name));
return rr->singular;
}
__C_DBG("Running RR interface selection for direction '" STR_FORMAT "'",
STR_FMT(name));
log = run_round_robin_calls(rr, num_ports);
if (log)
return log;
return __get_logical_interface(name, fam);
}
static struct logical_intf *__get_logical_interface(const str *name, sockfamily_t *fam) {
struct logical_intf d, *log = NULL;
d.name = *name;
d.preferred_family = fam;
log = g_hash_table_lookup(__logical_intf_name_family_hash, &d);
if (log) {
__C_DBG("Choose logical interface %.*s because of direction %.*s",
log->name.len, log->name.s,
name->len, name->s);
__C_DBG("Choose logical interface " STR_FORMAT " because of direction " STR_FORMAT,
STR_FMT(&log->name),
STR_FMT(name));
} else {
__C_DBG("Choose logical interface NULL because of direction %.*s",
name->len, name->s);
__C_DBG("Choose logical interface NULL because of direction " STR_FORMAT,
STR_FMT(name));
}
return log;
@ -482,26 +486,50 @@ int is_local_endpoint(const struct intf_address *addr, unsigned int port) {
}
// called during single-threaded startup only
static void __add_intf_rr_1(struct logical_intf *lif, str *name_base, sockfamily_t *fam) {
struct logical_intf key;
key.name = *name_base;
key.preferred_family = fam;
struct intf_rr *rr = g_hash_table_lookup(__logical_intf_name_family_rr_hash, &key);
if (!rr) {
rr = g_slice_alloc0(sizeof(*rr));
rr->hash_key = key;
mutex_init(&rr->lock);
g_hash_table_insert(__logical_intf_name_family_rr_hash, &rr->hash_key, rr);
}
g_queue_push_tail(&rr->logical_intfs, lif);
rr->singular = (rr->logical_intfs.length == 1) ? lif : NULL;
g_hash_table_insert(lif->rr_specs, &rr->hash_key.name, lif);
}
static void __add_intf_rr(struct logical_intf *lif, str *name_base, sockfamily_t *fam) {
__add_intf_rr_1(lif, name_base, fam);
static str legacy_rr_str = STR_CONST_INIT("round-robin-calls");
__add_intf_rr_1(lif, &legacy_rr_str, fam);
}
static GQueue *__interface_list_for_family(sockfamily_t *fam) {
return &__preferred_lists_for_family[fam->idx];
}
// called during single-threaded startup only
static void __interface_append(struct intf_config *ifa, sockfamily_t *fam) {
struct logical_intf *lif;
GQueue *q;
struct local_intf *ifc;
struct intf_spec *spec;
lif = get_logical_interface(&ifa->name, fam, 0);
lif = __get_logical_interface(&ifa->name, fam);
if (!lif) {
lif = g_slice_alloc0(sizeof(*lif));
lif->name = ifa->name;
lif->preferred_family = fam;
lif->addr_hash = g_hash_table_new(__addr_type_hash, __addr_type_eq);
lif->rr_specs = g_hash_table_new(str_hash, str_equal);
g_hash_table_insert(__logical_intf_name_family_hash, lif, lif);
if (ifa->local_address.addr.family == fam) {
q = __interface_list_for_family(fam);
g_queue_push_tail(q, lif);
__add_intf_rr(lif, &ifa->name_base, fam);
}
}
@ -527,6 +555,7 @@ static void __interface_append(struct intf_config *ifa, sockfamily_t *fam) {
__insert_local_intf_addr_type(&ifc->advertised_address, ifc);
}
// called during single-threaded startup only
void interfaces_init(GQueue *interfaces) {
int i;
GList *l;
@ -535,6 +564,7 @@ void interfaces_init(GQueue *interfaces) {
/* init everything */
__logical_intf_name_family_hash = g_hash_table_new(__name_family_hash, __name_family_eq);
__logical_intf_name_family_rr_hash = g_hash_table_new(__name_family_hash, __name_family_eq);
__intf_spec_addr_type_hash = g_hash_table_new(__addr_type_hash, __addr_type_eq);
__local_intf_addr_type_hash = g_hash_table_new(__addr_type_hash, __addr_type_eq);

@ -21,7 +21,8 @@ struct logical_intf {
str name;
sockfamily_t *preferred_family;
GQueue list; /* struct local_intf */
GHashTable *addr_hash;
GHashTable *addr_hash; // addr + type -> struct local_intf XXX obsolete?
GHashTable *rr_specs;
};
struct port_pool {
BIT_ARRAY_DECLARE(ports_used, 0x10000);
@ -35,7 +36,9 @@ struct intf_address {
sockaddr_t addr;
};
struct intf_config {
str name;
str name; // full name (before the '/' separator in config)
str name_base; // if name is "foo:bar", this is "foo"
str name_rr_spec; // if name is "foo:bar", this is "bar"
struct intf_address local_address;
struct intf_address advertised_address;
unsigned int port_min, port_max;

@ -70,7 +70,7 @@ INLINE void str_swap(str *a, str *b);
INLINE int str_to_i(str *s, int def);
/* parses a string uinto an int, returns default if conversion fails */
INLINE uint str_to_ui(str *s, int def);
/* extracts the first/next token into "new_token" and modifies "ori_and_remainer" in place */
/* extracts the first/next token into "new_token" and modifies "ori_and_remaidner" in place */
INLINE int str_token(str *new_token, str *ori_and_remainder, int sep);
/* same as str_token but allows for a trailing non-empty token (e.g. "foo,bar" -> "foo", "bar" ) */
INLINE int str_token_sep(str *new_token, str *ori_and_remainder, int sep);

@ -50,7 +50,7 @@ sub new {
$self->{mux} = IO::Multiplex->new();
$self->{mux}->set_callback_object($self);
$self->{media_port} = 2000;
$self->{media_port} = $args{media_port} // 2000;
$self->{timers} = [];
$self->{clients} = [];
@ -149,11 +149,13 @@ sub _new {
($args{sockdomain} && $args{sockdomain} != $address->{sockdomain}) and next;
my $rtp = IO::Socket::IP->new(Type => &SOCK_DGRAM, Proto => 'udp',
LocalHost => $address->{address}, LocalPort => $parent->{media_port}++)
or die($address->{address});
LocalHost => $address->{address}, LocalPort => $parent->{media_port})
or die("$address->{address}:$parent->{media_port}");
$parent->{media_port}++;
my $rtcp = IO::Socket::IP->new(Type => &SOCK_DGRAM, Proto => 'udp',
LocalHost => $address->{address}, LocalPort => $parent->{media_port}++)
or die($address->{address});
LocalHost => $address->{address}, LocalPort => $parent->{media_port})
or die("$address->{address}:$parent->{media_port}");
$parent->{media_port}++;
push(@sockets, [$rtp, $rtcp]); # component 0 and 1
push(@rtp, $rtp);
@ -271,7 +273,7 @@ sub _default_req_args {
my $req = { command => $cmd, 'call-id' => $self->{parent}->{callid} };
for my $cp (qw(sdp from-tag to-tag ICE transport-protocol address-family label)) {
for my $cp (qw(sdp from-tag to-tag ICE transport-protocol address-family label direction)) {
$args{$cp} and $req->{$cp} = $args{$cp};
}
for my $cp (@{$args{flags}}) {
@ -298,6 +300,7 @@ sub _offered {
my ($self, $req) = @_;
my $sdp_body = $req->{sdp} or die;
$self->{remote_sdp_raw} = $sdp_body;
$self->{remote_sdp} = NGCP::Rtpclient::SDP->decode($sdp_body);
# XXX validate SDP
@{$self->{remote_sdp}->{medias}} == 1 or die;
@ -323,6 +326,7 @@ sub _answered {
my ($self, $req) = @_;
my $sdp_body = $req->{sdp} or die;
$self->{remote_sdp_raw} = $sdp_body;
$self->{remote_sdp} = NGCP::Rtpclient::SDP->decode($sdp_body);
# XXX validate SDP
@{$self->{remote_sdp}->{medias}} == 1 or die;

@ -0,0 +1,55 @@
#!/usr/bin/perl
use strict;
use warnings;
use NGCP::Rtpengine::Test;
use IO::Socket;
my $iterations = 10;
my @interfaces = qw(int ext);
my @domains = (&Socket::AF_INET);
my $r = NGCP::Rtpengine::Test->new(media_port => 50000);
for my $a_domain (@domains) {
for my $b_domain (@domains) {
if (!@interfaces) {
for (1 .. $iterations) {
run_test([], $a_domain, $b_domain);
}
}
else {
for my $a_interface (@interfaces) {
for my $b_interface (@interfaces) {
for (1 .. $iterations) {
run_test([$a_interface, $b_interface], $a_domain, $b_domain);
}
}
}
}
}
}
sub run_test {
my ($directions, $a_domain, $b_domain) = @_;
print("Testing directions @{$directions} between $a_domain and $b_domain\n");
my ($a, $b) = $r->client_pair(
{sockdomain => $a_domain},
{sockdomain => $b_domain}
);
print("Offering with address: " . $a->{sockets}->[0]->[0]->sockhost . "\n");
my %dir_arg = ();
$dir_arg{direction} = $directions if @{$directions};
$a->offer($b, ICE => 'remove', label => "caller", %dir_arg);
print("Offer out with address: " . $b->{remote_media}->connection->{address} . "\n");
print("Answering with address: " . $b->{sockets}->[0]->[0]->sockhost . "\n");
$b->answer($a, ICE => 'remove', label => "callee");
print("Answer out with address: " . $a->{remote_media}->connection->{address} . "\n");
$a->teardown();
print("\n");
}
Loading…
Cancel
Save