If for some reason, you can’t run Icecast on port number 80, but you want to be able to access it on that port when coming from the outside world so that users behind firewall can access it easily.

For example, you want to access your stream through http://radio.domain.com/myStream.mp3.

Let’s say that you have Icecast running on your private server radio.private.domain.com on port 8000. You can have the below configuration (Apache 2 example), so that traffic going to http://radio.domain.com:80/ is passed by your reverse proxy to your Icecast private server on http://radio.private.domain.com:8000/:

<VirtualHost *:80>
    ServerName radio.domain.com
    ServerAdmin hostmaster@domain.com

    ProxyPreserveHost On
    ProxyPass / http://radio.private.domain.com:8000/
    ProxyPassReverse / http://radio.private.domain.com:8000/
</VirtualHost>

The problem which arises is that Icecast will report to YP (http://dir.xiph.org) with its listening port, that is by default the 8000 one, and not the one reachable from the outside world (80) which can be problematic you don’t want/can open your firewall on port 8000.

The same problem occurs for m3u and xspf files generation.
So the goal is to make Icecast m3u and xspf files generation and YP reports, to use the outside world reachable port.

In order to achieve that, you will need to download Icecast source code (version 2.3.2), apply the below patch on it, built it, and install it.

The you’ll need to add a new configuration <exposed-port> tag to the icecast.xml configuration file.

<exposed-port>80</exposed-port>

As root, launch the following commands:

$ tar zxvf icecast-2.3.2.tar.gz # from where the icecast archive is located
$ cd icecast-2.3.2
$ patch -p1 <../path/to/exposed_port.icecast.patch # patch the source code to add the exposed-port tag
$ make
$ make install # to /usr/local
$ mv /usr/bin/icecast2 /usr/bin/icecast2.original # Backup you current icecast binary
$ ln -s /usr/local/bin/icecast icecast2 # And replace it with the just built one.
$ service icecast2 restart

Copy/paste the below patch to file exposed_port.icecast.patch:
WARNING: This code has not been tested thoroughly yet (neither by me or someone else), so use it at your own risks !

diff --git a/src/cfgfile.c b/src/cfgfile.c
index 5eac93d..9fd2cb1 100644
--- a/src/cfgfile.c
+++ b/src/cfgfile.c
@@ -441,6 +441,11 @@ static void _parse_root(xmlDocPtr doc, xmlNodePtr node,
             configuration->port = atoi(tmp);
             configuration->listen_sock->port = atoi(tmp);
             if (tmp) xmlFree(tmp);
+        } else if (xmlStrcmp (node->name, XMLSTR("exposed-port")) == 0) {
+            tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
+            configuration->exposed_port = atoi(tmp);
+            configuration->listen_sock->exposed_port = atoi(tmp);
+            if (tmp) xmlFree(tmp);
         } else if (xmlStrcmp (node->name, XMLSTR("bind-address")) == 0) {
             if (configuration->listen_sock->bind_address) 
                 xmlFree(configuration->listen_sock->bind_address);
@@ -801,6 +806,13 @@ static void _parse_listen_socket(xmlDocPtr doc, xmlNodePtr node,
             listener->port = atoi(tmp);
             if(tmp) xmlFree(tmp);
         }
+        else if (xmlStrcmp (node->name, XMLSTR("exposed-port")) == 0) {
+            tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
+            if(configuration->exposed_port == 0)
+                configuration->exposed_port = atoi(tmp);
+            listener->exposed_port = atoi(tmp);
+            if(tmp) xmlFree(tmp);
+        }
         else if (xmlStrcmp (node->name, XMLSTR("ssl")) == 0) {
             tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
             listener->ssl = atoi(tmp);
diff --git a/src/cfgfile.h b/src/cfgfile.h
index fa5aa69..c48cb2d 100644
--- a/src/cfgfile.h
+++ b/src/cfgfile.h
@@ -101,6 +101,7 @@ typedef struct _aliases {
 typedef struct _listener_t {
     struct _listener_t *next;
     int port;
+    int exposed_port;
     char *bind_address;
     int shoutcast_compat;
     char *shoutcast_mount;
@@ -138,6 +139,7 @@ typedef struct ice_config_tag
 
     char *hostname;
     int port;
+    int exposed_port;
     char *mimetypes_fn;
 
     listener_t *listen_sock;
diff --git a/src/fserve.c b/src/fserve.c
index 724aaaf..0d31748 100644
--- a/src/fserve.c
+++ b/src/fserve.c
@@ -443,7 +443,7 @@ int fserve_client_create (client_t *httpclient, const char *path)
                     "HTTP/1.0 200 OK\r\n"
                     "Content-Type: audio/x-mpegurl\r\n\r\n"
                     "http://%s:%d%s\r\n", 
-                    config->hostname, config->port,
+                    config->hostname, ( config->exposed_port ? config->exposed_port : config->port ),
                     sourceuri
                     );
             config_release_config();
diff --git a/src/source.c b/src/source.c
index 02bfc74..a593936 100644
--- a/src/source.c
+++ b/src/source.c
@@ -579,7 +579,7 @@ static void source_init (source_t *source)
     listenurl = malloc (listen_url_size);
     memset (listenurl, '00', listen_url_size);
     snprintf (listenurl, listen_url_size, "http://%s:%d%s",
-            config->hostname, config->port, source->mount);
+            config->hostname, ( config->exposed_port ? config->exposed_port : config->port ), source->mount);
     config_release_config();
 
     str = httpp_getvar(source->parser, "ice-audio-info");
diff --git a/src/yp.c b/src/yp.c
index 4470d47..0c45577 100644
--- a/src/yp.c
+++ b/src/yp.c
@@ -584,12 +584,12 @@ static ypdata_t *create_yp_entry (const char *mount)
         if (url == NULL)
             break;
         config = config_get_config();
-        ret = snprintf (url, len, "http://%s:%d%s", config->hostname, config->port, mount);
+        ret = snprintf (url, len, "http://%s:%d%s", config->hostname, ( config->exposed_port ? config->exposed_port : config->port ), mount);
         if (ret >= (signed)len)
         {
             s = realloc (url, ++ret);
             if (s) url = s;
-            snprintf (url, ret, "http://%s:%d%s", config->hostname, config->port, mount);
+            snprintf (url, ret, "http://%s:%d%s", config->hostname, ( config->exposed_port ? config->exposed_port : config->port ), mount);
         }
 
         mountproxy = config_find_mount (config, mount);

Note: Below is the same patch for Icecast 2.4.1 source code

diff --git a/src/cfgfile.c b/src/cfgfile.c
index a9df53c..1c53358 100644
--- a/src/cfgfile.c
+++ b/src/cfgfile.c
@@ -509,6 +509,13 @@ static void _parse_root(xmlDocPtr doc, xmlNodePtr node,
             } else {
                 ICECAST_LOG_WARN("<port> must not be empty.");
             }
+        } else if (xmlStrcmp (node->name, XMLSTR("exposed-port")) == 0) {
+            tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
+            if (tmp) {
+                configuration->exposed_port = atoi(tmp);
+                configuration->listen_sock->exposed_port = atoi(tmp);
+                xmlFree(tmp);
+            }
         } else if (xmlStrcmp (node->name, XMLSTR("bind-address")) == 0) {
             if (configuration->listen_sock->bind_address) 
                 xmlFree(configuration->listen_sock->bind_address);
@@ -995,6 +1002,15 @@ static void _parse_listen_socket(xmlDocPtr doc, xmlNodePtr node,
                 ICECAST_LOG_WARN("<port> must not be empty.");
             }
         }
+        else if (xmlStrcmp (node->name, XMLSTR("exposed-port")) == 0) {
+            tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
+            if (tmp) {
+                if(configuration->exposed_port == 0)
+                    configuration->exposed_port = atoi(tmp);
+                listener->exposed_port = atoi(tmp);
+                xmlFree(tmp);
+            }
+        }
         else if (xmlStrcmp (node->name, XMLSTR("ssl")) == 0) {
             tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
             listener->ssl = atoi(tmp);
diff --git a/src/cfgfile.h b/src/cfgfile.h
index 387aac8..020819a 100644
--- a/src/cfgfile.h
+++ b/src/cfgfile.h
@@ -131,6 +131,7 @@ typedef struct _aliases {
 typedef struct _listener_t {
     struct _listener_t *next;
     int port;
+    int exposed_port;
     int so_sndbuf;
     char *bind_address;
     int shoutcast_compat;
@@ -168,6 +169,7 @@ typedef struct ice_config_tag {
 
     char *hostname;
     int port;
+    int exposed_port;
     char *mimetypes_fn;
 
     listener_t *listen_sock;
diff --git a/src/fserve.c b/src/fserve.c
index fd8d496..de7a23d 100644
--- a/src/fserve.c
+++ b/src/fserve.c
@@ -469,7 +469,7 @@ int fserve_client_create (client_t *httpclient, const char *path)
 	    config = config_get_config();
             snprintf (httpclient->refbuf->data + ret, BUFSIZE - ret,
                     "http://%s:%d%s\r\n", 
-                    config->hostname, config->port,
+                    config->hostname, ( config->exposed_port ? config->exposed_port : config->port ),
                     sourceuri
                     );
             config_release_config();
diff --git a/src/source.c b/src/source.c
index 0ffd65d..6254a76 100644
--- a/src/source.c
+++ b/src/source.c
@@ -630,7 +630,7 @@ static void source_init (source_t *source)
     listenurl = malloc (listen_url_size);
     memset (listenurl, '00', listen_url_size);
     snprintf (listenurl, listen_url_size, "http://%s:%d%s",
-            config->hostname, config->port, source->mount);
+            config->hostname, ( config->exposed_port ? config->exposed_port : config->port ), source->mount);
     config_release_config();
 
     str = httpp_getvar(source->parser, "ice-audio-info");
@@ -1226,7 +1226,7 @@ void source_update_settings (ice_config_t *config, source_t *source, mount_proxy
     source->burst_size = config->burst_size;
 
     stats_event_args (source->mount, "listenurl", "http://%s:%d%s",
-            config->hostname, config->port, source->mount);
+            config->hostname, ( config->exposed_port ? config->exposed_port : config->port ), source->mount);
 
     source_apply_mount (source, mountinfo);
 
@@ -1484,7 +1484,7 @@ void source_recheck_mounts (int update_all)
             {
                 stats_event_hidden (mount->mountname, NULL, mount->hidden);
                 stats_event_args (mount->mountname, "listenurl", "http://%s:%d%s",
-                        config->hostname, config->port, mount->mountname);
+                        config->hostname, ( config->exposed_port ? config->exposed_port : config->port ), mount->mountname);
                 stats_event (mount->mountname, "listeners", "0");
                 if (mount->max_listeners < 0)
                     stats_event (mount->mountname, "max_listeners", "unlimited");
diff --git a/src/yp.c b/src/yp.c
index 54d697b..e06ce71 100644
--- a/src/yp.c
+++ b/src/yp.c
@@ -585,12 +585,12 @@ static ypdata_t *create_yp_entry (const char *mount)
         if (url == NULL)
             break;
         config = config_get_config();
-        ret = snprintf (url, len, "http://%s:%d%s", config->hostname, config->port, mount);
+        ret = snprintf (url, len, "http://%s:%d%s", config->hostname, ( config->exposed_port ? config->exposed_port : config->port ), mount);
         if (ret >= (signed)len)
         {
             s = realloc (url, ++ret);
             if (s) url = s;
-            snprintf (url, ret, "http://%s:%d%s", config->hostname, config->port, mount);
+            snprintf (url, ret, "http://%s:%d%s", config->hostname, ( config->exposed_port ? config->exposed_port : config->port ), mount);
         }
 
         mountproxy = config_find_mount (config, mount, MOUNT_TYPE_NORMAL);

2 thoughts on “Icecast reachable behind reverse proxy

Leave a comment