From 1aa9944fe4c98707eaae43155ca3dc2541dddb15 Mon Sep 17 00:00:00 2001 From: Richard Fuchs Date: Thu, 21 Dec 2017 11:18:08 -0500 Subject: [PATCH] TT#27550 implement interface round-robin selection Change-Id: Id5cf290cc9d044716b5f55cf416dc40b87f23f24 --- README.md | 117 ++++++++++++++++--------- daemon/aux.h | 3 - daemon/call.c | 62 +++----------- daemon/main.c | 4 + daemon/media_socket.c | 164 +++++++++++++++++++++--------------- daemon/media_socket.h | 7 +- lib/str.h | 2 +- perl/NGCP/Rtpengine/Test.pm | 16 ++-- t/test-interfaces.pl | 55 ++++++++++++ 9 files changed, 260 insertions(+), 170 deletions(-) create mode 100755 t/test-interfaces.pl diff --git a/README.md b/README.md index 32c3804e6..b92bcd470 100644 --- a/README.md +++ b/README.md @@ -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 --------------------------- diff --git a/daemon/aux.h b/daemon/aux.h index 1b15dfe4c..1fe463c3a 100644 --- a/daemon/aux.h +++ b/daemon/aux.h @@ -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; diff --git a/daemon/call.c b/daemon/call.c index 54a1da980..8e992965a 100644 --- a/daemon/call.c +++ b/daemon/call.c @@ -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: diff --git a/daemon/main.c b/daemon/main.c index 1c3a00c6b..b90e70b83 100644 --- a/daemon/main.c +++ b/daemon/main.c @@ -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; } diff --git a/daemon/media_socket.c b/daemon/media_socket.c index c6228f238..b9381ab52 100644 --- a/daemon/media_socket.c +++ b/daemon/media_socket.c @@ -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); diff --git a/daemon/media_socket.h b/daemon/media_socket.h index 7886a2a5b..173f8e9f6 100644 --- a/daemon/media_socket.h +++ b/daemon/media_socket.h @@ -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; diff --git a/lib/str.h b/lib/str.h index ee3318ea7..0ebe2cf4b 100644 --- a/lib/str.h +++ b/lib/str.h @@ -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); diff --git a/perl/NGCP/Rtpengine/Test.pm b/perl/NGCP/Rtpengine/Test.pm index ab090d04a..4a572abef 100644 --- a/perl/NGCP/Rtpengine/Test.pm +++ b/perl/NGCP/Rtpengine/Test.pm @@ -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; diff --git a/t/test-interfaces.pl b/t/test-interfaces.pl new file mode 100755 index 000000000..a4fb31658 --- /dev/null +++ b/t/test-interfaces.pl @@ -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"); +}