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 @@

Type of Relays

will also periodically check the master server to see if any new mountpoints have attached and if so will relay those as well.

+

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 Relay

+
+

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);