Skip to content

Commit

Permalink
Allow specifying multiple IP addresses for 1-1 NAT. (#2279)
Browse files Browse the repository at this point in the history
  • Loading branch information
fancycode committed Jul 23, 2020
1 parent 7c41b0e commit ca4e3a3
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 66 deletions.
3 changes: 3 additions & 0 deletions conf/janus.jcfg.sample.in
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,9 @@ nat: {
# If you'd rather keep the private IP address in place, rather than
# replacing it (and so have both of them as advertised candidates),
# then set the 'keep_private_host' property to true.
# Multiple public IP addresses can be specified as a comma separated list
# if the Janus is deployed in a DMZ between two 1-1 NAT for internal and
# external users.
#nat_1_1_mapping = "1.2.3.4"
#keep_private_host = true

Expand Down
95 changes: 54 additions & 41 deletions ice.c
Original file line number Diff line number Diff line change
Expand Up @@ -1063,7 +1063,7 @@ int janus_ice_set_stun_server(gchar *stun_server, uint16_t stun_port) {
}
const char *public_ip = janus_network_address_string_from_buffer(&addr_buf);
JANUS_LOG(LOG_INFO, " >> Our public address is %s\n", public_ip);
janus_set_public_ip(public_ip);
janus_add_public_ip(public_ip);
return 0;
}

Expand Down Expand Up @@ -1980,7 +1980,7 @@ static void janus_ice_cb_new_selected_pair (NiceAgent *agent, guint stream_id, g
}

/* Candidates management */
static int janus_ice_candidate_to_string(janus_ice_handle *handle, NiceCandidate *c, char *buffer, int buflen, gboolean log_candidate, gboolean force_private);
static int janus_ice_candidate_to_string(janus_ice_handle *handle, NiceCandidate *c, char *buffer, int buflen, gboolean log_candidate, gboolean force_private, guint public_ip_index);
#ifndef HAVE_LIBNICE_TCP
static void janus_ice_cb_new_local_candidate (NiceAgent *agent, guint stream_id, guint component_id, gchar *foundation, gpointer ice) {
#else
Expand Down Expand Up @@ -2054,23 +2054,27 @@ static void janus_ice_cb_new_local_candidate (NiceAgent *agent, NiceCandidate *c
}
#endif
char buffer[200];
if(janus_ice_candidate_to_string(handle, candidate, buffer, sizeof(buffer), TRUE, FALSE) == 0) {
/* Candidate encoded, send a "trickle" event to the browser (but only if it's not a 'prflx') */
if(candidate->type == NICE_CANDIDATE_TYPE_PEER_REFLEXIVE) {
JANUS_LOG(LOG_VERB, "[%"SCNu64"] Skipping prflx candidate...\n", handle->handle_id);
} else {
janus_ice_notify_trickle(handle, buffer);
/* If nat-1-1 is enabled but we want to keep the private host, add another candidate */
if(nat_1_1_enabled && keep_private_host &&
janus_ice_candidate_to_string(handle, candidate, buffer, sizeof(buffer), TRUE, TRUE) == 0) {
if(candidate->type == NICE_CANDIDATE_TYPE_PEER_REFLEXIVE) {
JANUS_LOG(LOG_VERB, "[%"SCNu64"] Skipping prflx candidate...\n", handle->handle_id);
} else {
janus_ice_notify_trickle(handle, buffer);
guint public_ip_index = 0;
do {
if(janus_ice_candidate_to_string(handle, candidate, buffer, sizeof(buffer), TRUE, FALSE, public_ip_index) == 0) {
/* Candidate encoded, send a "trickle" event to the browser (but only if it's not a 'prflx') */
if(candidate->type == NICE_CANDIDATE_TYPE_PEER_REFLEXIVE) {
JANUS_LOG(LOG_VERB, "[%"SCNu64"] Skipping prflx candidate...\n", handle->handle_id);
} else {
janus_ice_notify_trickle(handle, buffer);
/* If nat-1-1 is enabled but we want to keep the private host, add another candidate */
if(nat_1_1_enabled && keep_private_host && public_ip_index == 0 &&
janus_ice_candidate_to_string(handle, candidate, buffer, sizeof(buffer), TRUE, TRUE, public_ip_index) == 0) {
if(candidate->type == NICE_CANDIDATE_TYPE_PEER_REFLEXIVE) {
JANUS_LOG(LOG_VERB, "[%"SCNu64"] Skipping prflx candidate...\n", handle->handle_id);
} else {
janus_ice_notify_trickle(handle, buffer);
}
}
}
}
}
public_ip_index++;
} while (public_ip_index < janus_get_public_ip_count());

#ifndef HAVE_LIBNICE_TCP
nice_candidate_free(candidate);
Expand Down Expand Up @@ -3030,7 +3034,7 @@ void janus_ice_incoming_data(janus_ice_handle *handle, char *label, char *protoc


/* Helper: encoding local candidates to string/SDP */
static int janus_ice_candidate_to_string(janus_ice_handle *handle, NiceCandidate *c, char *buffer, int buflen, gboolean log_candidate, gboolean force_private) {
static int janus_ice_candidate_to_string(janus_ice_handle *handle, NiceCandidate *c, char *buffer, int buflen, gboolean log_candidate, gboolean force_private, guint public_ip_index) {
if(!handle || !handle->agent || !c || !buffer || buflen < 1)
return -1;
janus_ice_stream *stream = handle->stream;
Expand All @@ -3042,7 +3046,7 @@ static int janus_ice_candidate_to_string(janus_ice_handle *handle, NiceCandidate
char *host_ip = NULL;
if(nat_1_1_enabled && !force_private) {
/* A 1:1 NAT mapping was specified, either overwrite all the host addresses with the public IP, or add new candidates */
host_ip = janus_get_public_ip();
host_ip = janus_get_public_ip(public_ip_index);
JANUS_LOG(LOG_VERB, "[%"SCNu64"] Public IP specified and 1:1 NAT mapping enabled (%s), using that as host address in the candidates\n", handle->handle_id, host_ip);
}
/* Encode the candidate to a string */
Expand Down Expand Up @@ -3210,25 +3214,29 @@ void janus_ice_candidates_to_sdp(janus_ice_handle *handle, janus_sdp_mline *mlin
gboolean log_candidates = (component->local_candidates == NULL);
for(i = candidates; i; i = i->next) {
NiceCandidate *c = (NiceCandidate *) i->data;
if(janus_ice_candidate_to_string(handle, c, buffer, sizeof(buffer), log_candidates, FALSE) == 0) {
/* Candidate encoded, add to the SDP (but only if it's not a 'prflx') */
if(c->type == NICE_CANDIDATE_TYPE_PEER_REFLEXIVE) {
JANUS_LOG(LOG_VERB, "[%"SCNu64"] Skipping prflx candidate...\n", handle->handle_id);
} else {
janus_sdp_attribute *a = janus_sdp_attribute_create("candidate", "%s", buffer);
mline->attributes = g_list_append(mline->attributes, a);
if(nat_1_1_enabled && keep_private_host &&
janus_ice_candidate_to_string(handle, c, buffer, sizeof(buffer), log_candidates, TRUE) == 0) {
/* Candidate with private host encoded, add to the SDP (but only if it's not a 'prflx') */
if(c->type == NICE_CANDIDATE_TYPE_PEER_REFLEXIVE) {
JANUS_LOG(LOG_VERB, "[%"SCNu64"] Skipping prflx candidate...\n", handle->handle_id);
} else {
janus_sdp_attribute *a = janus_sdp_attribute_create("candidate", "%s", buffer);
mline->attributes = g_list_append(mline->attributes, a);
guint public_ip_index = 0;
do {
if(janus_ice_candidate_to_string(handle, c, buffer, sizeof(buffer), log_candidates, FALSE, public_ip_index) == 0) {
/* Candidate encoded, add to the SDP (but only if it's not a 'prflx') */
if(c->type == NICE_CANDIDATE_TYPE_PEER_REFLEXIVE) {
JANUS_LOG(LOG_VERB, "[%"SCNu64"] Skipping prflx candidate...\n", handle->handle_id);
} else {
janus_sdp_attribute *a = janus_sdp_attribute_create("candidate", "%s", buffer);
mline->attributes = g_list_append(mline->attributes, a);
if(nat_1_1_enabled && keep_private_host && public_ip_index == 0 &&
janus_ice_candidate_to_string(handle, c, buffer, sizeof(buffer), log_candidates, TRUE, public_ip_index) == 0) {
/* Candidate with private host encoded, add to the SDP (but only if it's not a 'prflx') */
if(c->type == NICE_CANDIDATE_TYPE_PEER_REFLEXIVE) {
JANUS_LOG(LOG_VERB, "[%"SCNu64"] Skipping prflx candidate...\n", handle->handle_id);
} else {
janus_sdp_attribute *a = janus_sdp_attribute_create("candidate", "%s", buffer);
mline->attributes = g_list_append(mline->attributes, a);
}
}
}
}
}
public_ip_index++;
} while (public_ip_index < janus_get_public_ip_count());
nice_candidate_free(c);
}
/* Done */
Expand Down Expand Up @@ -3647,16 +3655,21 @@ void janus_ice_resend_trickles(janus_ice_handle *handle) {
nice_candidate_free(c);
continue;
}
if(janus_ice_candidate_to_string(handle, c, buffer, sizeof(buffer), FALSE, FALSE) == 0) {
/* Candidate encoded, send a "trickle" event to the browser */
janus_ice_notify_trickle(handle, buffer);
/* If nat-1-1 is enabled but we want to keep the private host, add another candidate */
if(nat_1_1_enabled && keep_private_host &&
janus_ice_candidate_to_string(handle, c, buffer, sizeof(buffer), FALSE, TRUE) == 0) {

guint public_ip_index = 0;
do {
if(janus_ice_candidate_to_string(handle, c, buffer, sizeof(buffer), FALSE, FALSE, public_ip_index) == 0) {
/* Candidate encoded, send a "trickle" event to the browser */
janus_ice_notify_trickle(handle, buffer);
/* If nat-1-1 is enabled but we want to keep the private host, add another candidate */
if(nat_1_1_enabled && keep_private_host && public_ip_index == 0 &&
janus_ice_candidate_to_string(handle, c, buffer, sizeof(buffer), FALSE, TRUE, public_ip_index) == 0) {
/* Candidate encoded, send a "trickle" event to the browser */
janus_ice_notify_trickle(handle, buffer);
}
}
}
public_ip_index++;
} while (public_ip_index < janus_get_public_ip_count());
nice_candidate_free(c);
}
/* Send a "completed" trickle at the end */
Expand Down
4 changes: 2 additions & 2 deletions janus.1
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ DTLS certificate key passphrase (if needed)
.BR \-S ", " \-\-stun-server=\fIip:port\fR
STUN server(:port) to use, if needed (e.g., Janus behind NAT, default=none)
.TP
.BR \-1 ", " \-\-nat-1-1=\fIip\fR
Public IP to put in all host candidates, assuming a 1:1 NAT is in place (e.g., Amazon EC2 instances, default=none)
.BR \-1 ", " \-\-nat-1-1=\fIips\fR
Comma-separated list of public IPs to put in all host candidates, assuming a 1:1 NAT is in place (e.g., Amazon EC2 instances, default=none)
.TP
.BR \-2 ", " \-\-keep-private-host
When nat-1-1 is used (e.g., Amazon EC2 instances), don't remove the private host, but keep both to simulate STUN (default=off)
Expand Down
76 changes: 61 additions & 15 deletions janus.c
Original file line number Diff line number Diff line change
Expand Up @@ -191,16 +191,33 @@ static gchar *local_ip = NULL;
gchar *janus_get_local_ip(void) {
return local_ip;
}
static gchar *public_ip = NULL;
gchar *janus_get_public_ip(void) {
/* Fallback to the local IP, if we have no public one */
return public_ip ? public_ip : local_ip;
static GHashTable *public_ips_table = NULL;
static GList *public_ips = NULL;
guint janus_get_public_ip_count(void) {
return public_ips_table ? g_hash_table_size(public_ips_table) : 0;
}
void janus_set_public_ip(const char *ip) {
/* once set do not override */
if(ip == NULL || public_ip != NULL)
gchar *janus_get_public_ip(guint index) {
if (!janus_get_public_ip_count()) {
/* Fallback to the local IP, if we have no public one */
return local_ip;
}
if (index >= g_hash_table_size(public_ips_table)) {
index = g_hash_table_size(public_ips_table) - 1;
}
return (char *)g_list_nth(public_ips, index)->data;
}
void janus_add_public_ip(const gchar *ip) {
if(ip == NULL) {
return;
public_ip = g_strdup(ip);
}

if(!public_ips_table) {
public_ips_table = g_hash_table_new_full(g_str_hash, g_str_equal, (GDestroyNotify)g_free, NULL);
}
if (g_hash_table_insert(public_ips_table, g_strdup(ip), NULL)) {
g_list_free(public_ips);
public_ips = g_hash_table_get_keys(public_ips_table);
}
}
static volatile gint stop = 0;
static gint stop_signal = 0;
Expand Down Expand Up @@ -292,8 +309,18 @@ static json_t *janus_info(const char *transaction) {
json_object_set_new(info, "candidates-timeout", json_integer(candidates_timeout));
json_object_set_new(info, "server-name", json_string(server_name ? server_name : JANUS_SERVER_NAME));
json_object_set_new(info, "local-ip", json_string(local_ip));
if(public_ip != NULL)
json_object_set_new(info, "public-ip", json_string(public_ip));
guint public_ip_count = janus_get_public_ip_count();
if(public_ip_count > 0) {
json_object_set_new(info, "public-ip", json_string(janus_get_public_ip(0)));
}
if(public_ip_count > 1) {
guint i;
json_t *ips = json_array();
for (i = 0; i < public_ip_count; i++) {
json_array_append_new(ips, json_string(janus_get_public_ip(i)));
}
json_object_set_new(info, "public-ips", ips);
}
json_object_set_new(info, "ipv6", janus_ice_is_ipv6_enabled() ? json_true() : json_false());
json_object_set_new(info, "ice-lite", janus_ice_is_ice_lite_enabled() ? json_true() : json_false());
json_object_set_new(info, "ice-tcp", janus_ice_is_ice_tcp_enabled() ? json_true() : json_false());
Expand Down Expand Up @@ -4580,11 +4607,24 @@ gint main(int argc, char *argv[])
item = janus_config_get(config, config_nat, janus_config_type_item, "nat_1_1_mapping");
if(item && item->value) {
JANUS_LOG(LOG_INFO, "Using nat_1_1_mapping for public IP: %s\n", item->value);
if(!janus_network_string_is_valid_address(janus_network_query_options_any_ip, item->value)) {
JANUS_LOG(LOG_WARN, "Invalid nat_1_1_mapping address %s, disabling...\n", item->value);
} else {
nat_1_1_mapping = item->value;
janus_set_public_ip(nat_1_1_mapping);
char **list = g_strsplit(item->value, ",", -1);
char *index = list[0];
if(index != NULL) {
int i=0;
while(index != NULL) {
if(strlen(index) > 0) {
if(!janus_network_string_is_valid_address(janus_network_query_options_any_ip, index)) {
JANUS_LOG(LOG_WARN, "Invalid nat_1_1_mapping address %s, skipping...\n", index);
} else {
janus_add_public_ip(index);
}
}
i++;
index = list[i];
}
}
g_strfreev(list);
if(janus_get_public_ip_count() > 0) {
/* Check if we should replace the private host, or advertise both candidates */
gboolean keep_private_host = FALSE;
item = janus_config_get(config, config_nat, janus_config_type_item, "keep_private_host");
Expand Down Expand Up @@ -5344,6 +5384,12 @@ gint main(int argc, char *argv[])

janus_recorder_deinit();
g_free(local_ip);
if (public_ips) {
g_list_free(public_ips);
}
if (public_ips_table) {
g_hash_table_destroy(public_ips_table);
}

if(janus_ice_get_static_event_loops() > 0)
janus_ice_stop_static_event_loops();
Expand Down
2 changes: 1 addition & 1 deletion janus.ggo
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ option "cert-pem" c "DTLS certificate" string typestr="filename" optional
option "cert-key" k "DTLS certificate key" string typestr="filename" optional
option "cert-pwd" K "DTLS certificate key passphrase (if needed)" string typestr="text" optional
option "stun-server" S "STUN server(:port) to use, if needed (e.g., Janus behind NAT, default=none)" string typestr="ip:port" optional
option "nat-1-1" 1 "Public IP to put in all host candidates, assuming a 1:1 NAT is in place (e.g., Amazon EC2 instances, default=none)" string typestr="ip" optional
option "nat-1-1" 1 "Comma-separated list of public IPs to put in all host candidates, assuming a 1:1 NAT is in place (e.g., Amazon EC2 instances, default=none)" string typestr="ips" optional
option "keep-private-host" 2 "When nat-1-1 is used (e.g., Amazon EC2 instances), don't remove the private host, but keep both to simulate STUN" flag off
option "ice-enforce-list" E "Comma-separated list of the only interfaces to use for ICE gathering; partial strings are supported (e.g., eth0 or eno1,wlan0, default=none)" string typestr="list" optional
option "ice-ignore-list" X "Comma-separated list of interfaces or IP addresses to ignore for ICE gathering; partial strings are supported (e.g., vmnet8,192.168.0.1,10.0.0.1 or vmnet,192.168., default=vmnet)" string typestr="list" optional
Expand Down
10 changes: 6 additions & 4 deletions janus.h
Original file line number Diff line number Diff line change
Expand Up @@ -271,10 +271,12 @@ gchar *janus_get_server_key(void);

/*! \brief Helper method to return the local IP address (autodetected by default) */
gchar *janus_get_local_ip(void);
/*! \brief Helper method to return the IP address to use in the SDP (autodetected by default) */
gchar *janus_get_public_ip(void);
/*! \brief Helper method to overwrite the IP address to use in the SDP */
void janus_set_public_ip(const char *ip);
/*! \brief Helper method to return a given public IP address to use in the SDP (if multiple are configured for 1-1 NAT) */
gchar *janus_get_public_ip(guint index);
/*! \brief Helper method to return the number of public IP addresses (if configured for 1-1 NAT) */
guint janus_get_public_ip_count(void);
/*! \brief Helper method to add an IP address to use in the SDP */
void janus_add_public_ip(const char *ip);
/*! \brief Helper method to check whether the server is being shut down */
gint janus_is_stopping(void);

Expand Down
6 changes: 3 additions & 3 deletions sdp.c
Original file line number Diff line number Diff line change
Expand Up @@ -1199,7 +1199,7 @@ char *janus_sdp_merge(void *ice_handle, janus_sdp *anon, gboolean offer) {
char *rtp_profile = handle->rtp_profile ? handle->rtp_profile : (char *)"UDP/TLS/RTP/SAVPF";
if(!janus_is_webrtc_encryption_enabled())
rtp_profile = (char *)"RTP/AVPF";
gboolean ipv4 = !strstr(janus_get_public_ip(), ":");
gboolean ipv4 = !strstr(janus_get_public_ip(0), ":");
/* Origin o= */
gint64 sessid = janus_get_real_time();
if(anon->o_name == NULL)
Expand All @@ -1210,7 +1210,7 @@ char *janus_sdp_merge(void *ice_handle, janus_sdp *anon, gboolean offer) {
}
anon->o_ipv4 = ipv4;
g_free(anon->o_addr);
anon->o_addr = g_strdup(janus_get_public_ip());
anon->o_addr = g_strdup(janus_get_public_ip(0));
/* Session name s= */
if(anon->s_name == NULL)
anon->s_name = g_strdup("Meetecho Janus");
Expand Down Expand Up @@ -1289,7 +1289,7 @@ char *janus_sdp_merge(void *ice_handle, janus_sdp *anon, gboolean offer) {
/* Media connection c= */
g_free(m->c_addr);
m->c_ipv4 = ipv4;
m->c_addr = g_strdup(janus_get_public_ip());
m->c_addr = g_strdup(janus_get_public_ip(0));
/* Check if we need to refuse the media or not */
if(m->type == JANUS_SDP_AUDIO) {
audio++;
Expand Down

0 comments on commit ca4e3a3

Please sign in to comment.