diff --git a/conf/icecast.xml.in b/conf/icecast.xml.in
index 4fa43da1..99c27822 100644
--- a/conf/icecast.xml.in
+++ b/conf/icecast.xml.in
@@ -108,6 +108,21 @@
+
+
+
+
diff --git a/doc/relaying.html b/doc/relaying.html
index 4c22d8a9..b9a71c51 100644
--- a/doc/relaying.html
+++ b/doc/relaying.html
@@ -35,6 +35,9 @@
This "master-slave" type relay has been extended to support aggregation so that multiple masters can be given
+and the slave will "aggregate" all of the mountpoints for those master servers.
+
The second type of relay is a “single-broadcast” relay. In this case, the slave server is configured with a
server IP, port and mount and only the mountpoint specified is relayed. In order to relay a broadcast stream on
a Shoutcast server, you must use the “single-broadcast” relay and specify a mountpoint of /.
@@ -61,6 +64,36 @@
+
Setting Up a Master-Slave Aggregating Relay
+
In order to setup a relay of this type all servers (the one you wish to relay and the one doing the relaying)
+need to be Icecast 2 servers. The following configuration snippet is used as an example:
+
+
<master-update-interval>120</master-update-interval>
+<master>
+ <server>192.168.1.11</server>
+ <port>8001</port>
+ <namespace>/upstream1</namespace>
+ <password>hackme</password>
+ <on-demand>1</on-demand>
+</master>
+<master>
+ <server>192.168.1.12</server>
+ <port>8001</port>
+ <password>hackme</password>
+</master>
+
+
+
In this example, this configuration is setup in the server which will be doing the relaying (slave server).
+The master servers in this case need not be configured (and actually they are unaware of the relaying being performed)
+as relays. When the slave server is started, it will connect to each of the master servers located at 192.168.1.11:8001
+and 192.168.1.12:8001 and will begin to relay all mountpoints connected to the master servers. Additionally,
+every master-update-interval (120 seconds in this case) the slave server will poll the master servers to see if any new
+mountpoints have connected, and if so, the slave server will relay those as well. Note that all mountpoints of the master
+server at 192.168.1.11:8001 will have the namespace "/upstream1" prepended to it's mountpoints.
+
+
+
Setting Up a Single-Broadcast Relay
In this case, the master server need not be an Icecast 2 server. Supported master servers for a single-broadcast
diff --git a/src/cfgfile.c b/src/cfgfile.c
index f8fde5de..bd9d0cbd 100644
--- a/src/cfgfile.c
+++ b/src/cfgfile.c
@@ -133,6 +133,7 @@ static void _parse_http_headers(xmlDocPtr doc,
xmlNodePtr node,
ice_config_http_header_t **http_headers);
+static void _parse_master(xmlDocPtr doc, xmlNodePtr node, ice_config_t *c);
static void _parse_relay(xmlDocPtr doc, xmlNodePtr node, ice_config_t *c);
static void _parse_mount(xmlDocPtr doc, xmlNodePtr node, ice_config_t *c);
@@ -487,6 +488,7 @@ void config_clear(ice_config_t *c)
{
ice_config_dir_t *dirnode,
*nextdirnode;
+ master_server *master;
relay_server *relay,
*nextrelay;
mount_proxy *mount,
@@ -529,6 +531,11 @@ void config_clear(ice_config_t *c)
while ((c->listen_sock = config_clear_listener(c->listen_sock)));
+ master = c->master;
+ while (master) {
+ master = master_free(master);
+ }
+
thread_mutex_lock(&(_locks.relay_lock));
relay = c->relay;
while (relay) {
@@ -934,6 +941,8 @@ static void _parse_root(xmlDocPtr doc,
_parse_limits(doc, node->xmlChildrenNode, configuration);
} else if (xmlStrcmp(node->name, XMLSTR("http-headers")) == 0) {
_parse_http_headers(doc, node->xmlChildrenNode, &(configuration->http_headers));
+ } else if (xmlStrcmp(node->name, XMLSTR("master")) == 0) {
+ _parse_master(doc, node->xmlChildrenNode, configuration);
} else if (xmlStrcmp(node->name, XMLSTR("relay")) == 0) {
_parse_relay(doc, node->xmlChildrenNode, configuration);
} else if (xmlStrcmp(node->name, XMLSTR("mount")) == 0) {
@@ -1579,6 +1588,81 @@ static void _parse_http_headers(xmlDocPtr doc,
xmlFree(value);
}
+static void _parse_master(xmlDocPtr doc,
+ xmlNodePtr node,
+ ice_config_t *configuration)
+{
+ char *tmp;
+ master_server *master = calloc(1, sizeof(master_server));
+ master_server *current = configuration->master;
+ master_server *last = NULL;
+
+ while(current) {
+ last = current;
+ current = current->next;
+ }
+
+ if (last) {
+ last->next = master;
+ } else {
+ configuration->master = master;
+ }
+
+ master->next = NULL;
+ master->on_demand = configuration->on_demand;
+ master->server = NULL;
+ master->username = (char *) xmlCharStrdup(configuration->master_username);
+ if (configuration->master_password)
+ master->password = (char *) xmlCharStrdup(configuration->master_password);
+ master->namespace = NULL;
+
+ do {
+ if (node == NULL)
+ break;
+ if (xmlIsBlankNode(node))
+ continue;
+
+ if (xmlStrcmp(node->name, XMLSTR("server")) == 0) {
+ if (master->server)
+ xmlFree(master->server);
+ master->server = (char *)xmlNodeListGetString(doc,
+ node->xmlChildrenNode, 1);
+ } else if (xmlStrcmp(node->name, XMLSTR("port")) == 0) {
+ tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
+ if (tmp) {
+ master->port = atoi(tmp);
+ xmlFree(tmp);
+ } else {
+ ICECAST_LOG_WARN(" setting must not be empty.");
+ }
+ } else if (xmlStrcmp(node->name, XMLSTR("username")) == 0) {
+ if (master->username)
+ xmlFree(master->username);
+ master->username = (char *)xmlNodeListGetString(doc,
+ node->xmlChildrenNode, 1);
+ } else if (xmlStrcmp(node->name, XMLSTR("password")) == 0) {
+ if (master->password)
+ xmlFree(master->password);
+ master->password = (char *)xmlNodeListGetString(doc,
+ node->xmlChildrenNode, 1);
+ } else if (xmlStrcmp(node->name, XMLSTR("namespace")) == 0) {
+ if (master->namespace)
+ xmlFree(master->namespace);
+ master->namespace = (char *)xmlNodeListGetString(doc,
+ node->xmlChildrenNode, 1);
+ } else if (xmlStrcmp(node->name, XMLSTR("on-demand")) == 0) {
+ tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
+ master->on_demand = util_str_to_bool(tmp);
+ if (tmp)
+ xmlFree(tmp);
+ }
+ } while ((node = node->next));
+
+ if (master->server == NULL) {
+ ICECAST_LOG_WARN(" is required for .");
+ }
+}
+
static void _parse_relay(xmlDocPtr doc,
xmlNodePtr node,
ice_config_t *configuration)
diff --git a/src/cfgfile.h b/src/cfgfile.h
index d18725c6..0b0938c0 100644
--- a/src/cfgfile.h
+++ b/src/cfgfile.h
@@ -218,6 +218,7 @@ typedef struct ice_config_tag {
/* is TLS supported by the server? */
int tls_ok;
+ master_server *master;
relay_server *relay;
mount_proxy *mounts;
diff --git a/src/slave.c b/src/slave.c
index 59c1b271..6b41c227 100644
--- a/src/slave.c
+++ b/src/slave.c
@@ -63,6 +63,104 @@ static volatile int update_all_mounts = 0;
static volatile unsigned int max_interval = 0;
static mutex_t _slave_mutex; // protects update_settings, update_all_mounts, max_interval
+
+/* free a master and return its next master */
+master_server *master_free(master_server *master)
+{
+ master_server *next = NULL;
+ if (master)
+ {
+ next = master->next;
+ ICECAST_LOG_DEBUG("freeing master %s:%d", master->server, master->port);
+ if (master->server)
+ xmlFree(master->server);
+ if (master->username)
+ xmlFree(master->username);
+ if (master->password)
+ xmlFree(master->password);
+ if (master->namespace)
+ xmlFree(master->namespace);
+ free(master);
+ }
+ return next;
+}
+
+
+/* copy a master and return the copy */
+static master_server *master_copy(master_server *master)
+{
+ master_server *copy = calloc(1, sizeof(*master));
+
+ if (copy)
+ {
+ copy->server = (char *)xmlCharStrdup(master->server);
+ if (master->username)
+ copy->username = (char *)xmlCharStrdup(master->username);
+ if (master->password)
+ copy->password = (char *)xmlCharStrdup(master->password);
+ if (master->namespace)
+ copy->namespace = (char *)xmlCharStrdup(master->namespace);
+ copy->port = master->port;
+ copy->on_demand = master->on_demand;
+ }
+ return copy;
+}
+
+
+/* free master list */
+static void master_list_free(master_server *list)
+{
+ while ((list = master_free(list)));
+}
+
+
+/* insert a master into a master list and return the list head */
+static master_server *master_list_insert(master_server *list, master_server *master)
+{
+ master->next = list;
+ return master;
+}
+
+
+/* copy a master list and return copied list */
+static master_server *master_list_copy(master_server *list) {
+ master_server *list_copy = NULL;
+
+ while (list)
+ {
+ master_server *copy = master_copy(list);
+ if (copy == NULL)
+ {
+ master_list_free(list_copy);
+ list_copy = NULL;
+ break;
+ }
+ list_copy = master_list_insert(list_copy, copy);
+ list = list->next;
+ }
+
+ return list_copy;
+}
+
+
+/* turn a legacy master (from config) into a master and return it */
+static master_server *master_from_legacy(ice_config_t *config) {
+ master_server *master = calloc(1, sizeof(*master));
+
+ if (master) {
+ master->username = strdup(config->master_username);
+ if (config->master_password)
+ master->password = strdup(config->master_password);
+ if (config->master_server)
+ master->server = strdup(config->master_server);
+ master->port = config->master_server_port;
+ master->on_demand = config->on_demand;
+ }
+
+ return master;
+}
+
+
relay_server *relay_free (relay_server *relay)
{
relay_server *next = relay->next;
@@ -598,10 +696,8 @@ static void relay_check_streams (relay_server *to_start,
}
-static int update_from_master(ice_config_t *config)
+static int update_from_master(master_server *master)
{
- char *master = NULL, *password = NULL, *username= NULL;
- int port;
sock_t mastersock;
int ret = 0;
char buf[256];
@@ -610,23 +706,11 @@ static int update_from_master(ice_config_t *config)
char *authheader, *data;
relay_server *new_relays = NULL, *cleanup_relays;
int len, count = 1;
- int on_demand;
-
- username = strdup(config->master_username);
- if (config->master_password)
- password = strdup(config->master_password);
-
- if (config->master_server)
- master = strdup(config->master_server);
-
- port = config->master_server_port;
- if (password == NULL || master == NULL || port == 0)
+ if (master->password == NULL || master->server == NULL || master->port == 0)
break;
- on_demand = config->on_demand;
ret = 1;
- config_release_config();
- mastersock = sock_connect_wto(master, port, 10);
+ mastersock = sock_connect_wto(master->server, master->port, 10);
if (mastersock == SOCK_ERROR)
{
@@ -634,9 +718,9 @@ static int update_from_master(ice_config_t *config)
break;
}
- len = strlen(username) + strlen(password) + 2;
+ len = strlen(master->username) + strlen(master->password) + 2;
authheader = malloc(len);
- snprintf (authheader, len, "%s:%s", username, password);
+ snprintf (authheader, len, "%s:%s", master->username, master->password);
data = util_base64_encode(authheader, len);
sock_write (mastersock,
"GET /admin/streamlist.txt HTTP/1.0\r\n"
@@ -682,14 +766,23 @@ static int update_from_master(ice_config_t *config)
}
else
{
- r->server = (char *)xmlCharStrdup (master);
- r->port = port;
+ r->server = (char *)xmlCharStrdup (master->server);
+ r->port = master->port;
}
r->mount = strdup(parsed_uri->path);
- r->localmount = strdup(parsed_uri->path);
+ if (master->namespace)
+ {
+ int mountlen = strlen(master->namespace) + strlen(parsed_uri->path) + 2;
+ r->localmount = malloc(mountlen);
+ snprintf(r->localmount, mountlen, "%s%s%s",
+ (master->namespace[0] == '/') ? "" : "/", master->namespace,
+ parsed_uri->path);
+ } else {
+ r->localmount = strdup(parsed_uri->path);
+ }
r->mp3metadata = 1;
- r->on_demand = on_demand;
+ r->on_demand = master->on_demand;
r->next = new_relays;
ICECAST_LOG_DEBUG("Added relay host=\"%s\", port=%d, mount=\"%s\"", r->server, r->port, r->mount);
new_relays = r;
@@ -708,17 +801,9 @@ static int update_from_master(ice_config_t *config)
} while(0);
- if (master)
- free (master);
- if (username)
- free (username);
- if (password)
- free (password);
-
return ret;
}
-
static void *_slave_thread(void *arg)
{
ice_config_t *config;
@@ -759,18 +844,41 @@ static void *_slave_thread(void *arg)
thread_mutex_lock(&_slave_mutex);
if (max_interval <= interval)
{
+ master_server *list = NULL;
+
ICECAST_LOG_DEBUG("checking master stream list");
- config = config_get_config();
if (max_interval == 0)
skip_timer = 1;
interval = 0;
+
+ config = config_get_config();
max_interval = config->master_update_interval;
+ config_release_config();
+
thread_mutex_unlock(&_slave_mutex);
+
+ /* copy master list and insert legacy master from config. note:
+ * keeping a global list of master servers that only changes on a
+ * config change would be much more efficient than performing a
+ * copy each time we do an update -- implement this (and the
+ * locking that goes with along with this approach) */
+ config = config_get_config();
+ list = master_list_insert(
+ master_list_copy(config->master),
+ master_from_legacy(config)
+ );
+ config_release_config();
+
+ /* update all master servers */
+ while (list) {
+ update_from_master(list);
+ list = list->next;
+ }
- /* the connection could take some time, so the lock can drop */
- if (update_from_master (config))
- config = config_get_config();
+ master_list_free(list);
+
+ config = config_get_config();
thread_mutex_lock (&(config_locks()->relay_lock));
diff --git a/src/slave.h b/src/slave.h
index d8ff50de..aa478a25 100644
--- a/src/slave.h
+++ b/src/slave.h
@@ -15,6 +15,16 @@
#include "common/thread/thread.h"
+typedef struct _master_server {
+ char *server;
+ int port;
+ char *username;
+ char *password;
+ int on_demand;
+ char *namespace;
+ struct _master_server *next;
+} master_server;
+
typedef struct _relay_server {
char *server;
int port;
@@ -34,6 +44,8 @@ typedef struct _relay_server {
} relay_server;
+master_server *master_free (master_server *master);
+
void slave_initialize(void);
void slave_shutdown(void);
void slave_update_all_mounts (void);