[PATCH] Support ICY protocol (ICECast / ICEShout servers) in Squid-3

View: New views
4 Messages — Rating Filter:   Alert me  

[PATCH] Support ICY protocol (ICECast / ICEShout servers) in Squid-3

by Amos Jeffries-2 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Since we have no central place where the headers are upgraded I've had
to skip porting the upgrade_http0.9 hack in Squid-2 and go straight to
accepting ICY protocol as an accepted response protocol and handling it.

Somewhat primitive for now. It's limited to parsing and regenerating the
status line correctly, and skipping the HTTP/1.0 version override on
non-HTTP protocol replies.

Since it is on port 80 I've temporarily left the HTTP/1.1 required
header alterations happening. Some testing will be needed over the next
few days to ensure that the client software treats unknown headers
nicely. If needed overrides for those are easily done as well now.

Amos

=== modified file 'src/HttpReply.cc'
--- src/HttpReply.cc 2009-08-22 10:43:54 +0000
+++ src/HttpReply.cc 2009-11-01 10:17:45 +0000
@@ -458,24 +458,33 @@
         return false;
     }
 
+    int pos;
     // catch missing or mismatched protocol identifier
-    if (protoPrefix.cmp(buf->content(), protoPrefix.size()) != 0) {
-        debugs(58, 3, "HttpReply::sanityCheckStartLine: missing protocol prefix (" << protoPrefix << ") in '" << buf->content() << "'");
-        *error = HTTP_INVALID_HEADER;
-        return false;
+    // allow special-case for ICY protocol (non-HTTP identifier) in response to faked HTTP request.
+    if (strncmp(buf->content(), "ICY", 3) == 0) {
+        protoPrefix = "ICY";
+        pos = protoPrefix.psize();
     }
-
-    // catch missing or negative status value (negative '-' is not a digit)
-    int pos = protoPrefix.psize();
-
-    // skip arbitrary number of digits and a dot in the verion portion
-    while ( pos <= buf->contentSize() && (*(buf->content()+pos) == '.' || xisdigit(*(buf->content()+pos)) ) ) ++pos;
-
-    // catch missing version info
-    if (pos == protoPrefix.psize()) {
-        debugs(58, 3, "HttpReply::sanityCheckStartLine: missing protocol version numbers (ie. " << protoPrefix << "/1.0) in '" << buf->content() << "'");
-        *error = HTTP_INVALID_HEADER;
-        return false;
+    else {
+
+        if (protoPrefix.cmp(buf->content(), protoPrefix.size()) != 0) {
+            debugs(58, 3, "HttpReply::sanityCheckStartLine: missing protocol prefix (" << protoPrefix << ") in '" << buf->content() << "'");
+            *error = HTTP_INVALID_HEADER;
+            return false;
+        }
+
+        // catch missing or negative status value (negative '-' is not a digit)
+        pos = protoPrefix.psize();
+
+        // skip arbitrary number of digits and a dot in the verion portion
+        while ( pos <= buf->contentSize() && (*(buf->content()+pos) == '.' || xisdigit(*(buf->content()+pos)) ) ) ++pos;
+
+        // catch missing version info
+        if (pos == protoPrefix.psize()) {
+            debugs(58, 3, "HttpReply::sanityCheckStartLine: missing protocol version numbers (ie. " << protoPrefix << "/1.0) in '" << buf->content() << "'");
+            *error = HTTP_INVALID_HEADER;
+            return false;
+        }
     }
 
     // skip arbitrary number of spaces...

=== modified file 'src/HttpStatusLine.cc'
--- src/HttpStatusLine.cc 2009-09-16 09:53:46 +0000
+++ src/HttpStatusLine.cc 2009-11-01 11:06:47 +0000
@@ -39,6 +39,7 @@
 /* local constants */
 /* AYJ: see bug 2469 - RFC2616 confirms stating 'SP characters' plural! */
 const char *HttpStatusLineFormat = "HTTP/%d.%d %3d %s\r\n";
+const char *IcyStatusLineFormat = "ICY %3d %s\r\n";
 
 void
 httpStatusLineInit(HttpStatusLine * sline)
@@ -59,17 +60,31 @@
 httpStatusLineSet(HttpStatusLine * sline, HttpVersion version, http_status status, const char *reason)
 {
     assert(sline);
+    sline->protocol = PROTO_HTTP;
     sline->version = version;
     sline->status = status;
     /* Note: no xstrdup for 'reason', assumes constant 'reasons' */
     sline->reason = reason;
 }
 
-/** write HTTP version and status structures into a Packer buffer for output as HTTP status line. */
+/**
+ * Write HTTP version and status structures into a Packer buffer for output as HTTP status line.
+ * Special exemption made for ICY response status lines.
+ */
 void
 httpStatusLinePackInto(const HttpStatusLine * sline, Packer * p)
 {
     assert(sline && p);
+
+    /* handle ICY protocol status line specially. Pass on the bad format. */
+    if (sline->protocol == PROTO_ICY) {
+        debugs(57, 9, "packing sline " << sline << " using " << p << ":");
+        debugs(57, 9, "FORMAT=" << IcyStatusLineFormat );
+        debugs(57, 9, "ICY " << sline->status << " " << (sline->reason ? sline->reason : httpStatusString(sline->status)) );
+        packerPrintf(p, IcyStatusLineFormat, sline->status, httpStatusLineReason(sline));
+        return;
+    }
+
     debugs(57, 9, "packing sline " << sline << " using " << p << ":");
     debugs(57, 9, "FORMAT=" << HttpStatusLineFormat );
     debugs(57, 9, "HTTP/" << sline->version.major << "." << sline->version.minor <<
@@ -91,17 +106,24 @@
     // XXX: HttpMsg::parse() has a similar check but is using
     // casesensitive comparison (which is required by HTTP errata?)
 
-    if (protoPrefix.caseCmp(start, protoPrefix.size()) != 0)
-        return 0;
-
-    start += protoPrefix.size();
-
-    if (!xisdigit(*start))
-        return 0;
-
-    if (sscanf(start, "%d.%d", &sline->version.major, &sline->version.minor) != 2) {
-        debugs(57, 7, "httpStatusLineParse: Invalid HTTP identifier.");
-    }
+    if (protoPrefix.cmp("ICY", 3) == 0) {
+        debugs(57, 3, "httpStatusLineParse: Invalid HTTP identifier. Detected ICY protocol istead.");
+        sline->protocol = PROTO_ICY;
+        start += protoPrefix.size();
+    }
+    else if (protoPrefix.caseCmp(start, protoPrefix.size()) != 0) {
+
+        start += protoPrefix.size();
+
+        if (!xisdigit(*start))
+            return 0;
+
+        if (sscanf(start, "%d.%d", &sline->version.major, &sline->version.minor) != 2) {
+            debugs(57, 7, "httpStatusLineParse: Invalid HTTP identifier.");
+        }
+    }
+    else
+        return 0;
 
     if (!(start = strchr(start, ' ')))
         return 0;

=== modified file 'src/HttpStatusLine.h'
--- src/HttpStatusLine.h 2009-01-21 03:47:47 +0000
+++ src/HttpStatusLine.h 2009-11-01 08:20:01 +0000
@@ -39,20 +39,32 @@
 /* for SQUIDCEXTERN */
 #include "config.h"
 
-/* for http_status */
+/* for http_status and protocol_t */
 #include "enums.h"
 
-/* for class variables */
 #include "HttpVersion.h"
+#include "SquidString.h"
 
+/**
+ * Holds the values parsed from an HTTP reply status line.
+ *
+ * For example: HTTP/1.1 200 Okay
+ */
 class HttpStatusLine
 {
-
 public:
     /* public, read only */
-    HttpVersion version;
-    const char *reason; /**< points to a _constant_ string (default or supplied), never free()d */
-    http_status status;
+
+    /**
+     * By rights protocol name should be a constant "HTTP", with no need for this field to exist.
+     * However there are protocols which violate HTTP by sending their wn custom formats
+     * back with other protocol names (ICY streaming format being the current major problem)
+     */
+    protocol_t protocol;
+
+    HttpVersion version;     ///< breakdown of protocol version labels: 0.9 1.0 1.1
+    http_status status;      ///< status code. ie 200 404
+    const char *reason;     ///< points to a _constant_ string (default or supplied), never free()d */
 };
 
 /* init/clean */

=== modified file 'src/URLScheme.cc'
--- src/URLScheme.cc 2009-01-21 03:47:47 +0000
+++ src/URLScheme.cc 2009-11-01 11:25:59 +0000
@@ -52,6 +52,6 @@
     "whois",
     "internal",
     "https",
+    "icy",
     "TOTAL"
 };
-

=== modified file 'src/client_side_reply.cc'
--- src/client_side_reply.cc 2009-09-18 20:40:02 +0000
+++ src/client_side_reply.cc 2009-11-01 10:40:59 +0000
@@ -1440,8 +1440,10 @@
 
     reply = HTTPMSGLOCK(rep);
 
-    /* enforce 1.0 reply version */
-    reply->sline.version = HttpVersion(1,0);
+    if (reply->sline.protocol == PROTO_HTTP) {
+        /* enforce 1.0 reply version (but only on real HTTP traffic) */
+        reply->sline.version = HttpVersion(1,0);
+    }
 
     /* do header conversions */
     buildReplyHeader();

=== modified file 'src/enums.h'
--- src/enums.h 2009-08-23 05:13:09 +0000
+++ src/enums.h 2009-11-01 08:18:48 +0000
@@ -152,6 +152,7 @@
     PROTO_WHOIS,
     PROTO_INTERNAL,
     PROTO_HTTPS,
+    PROTO_ICY,
     PROTO_MAX
 } protocol_t;
 


Re: [PATCH] Support ICY protocol (ICECast / ICEShout servers) in Squid-3

by Amos Jeffries-2 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Amos Jeffries wrote:

> Since we have no central place where the headers are upgraded I've had
> to skip porting the upgrade_http0.9 hack in Squid-2 and go straight to
> accepting ICY protocol as an accepted response protocol and handling it.
>
> Somewhat primitive for now. It's limited to parsing and regenerating the
> status line correctly, and skipping the HTTP/1.0 version override on
> non-HTTP protocol replies.
>
> Since it is on port 80 I've temporarily left the HTTP/1.1 required
> header alterations happening. Some testing will be needed over the next
> few days to ensure that the client software treats unknown headers
> nicely. If needed overrides for those are easily done as well now.
>
> Amos
>

Now tested with VLC and WinAmp.

Looks like a success for the ICY changes.  Sounds artifacts and
stuttering occurring regularly 1/sec without the patch. A seamless
stream of sound when it's added.

The patch as given breaks HTTP with an incorrectly inverted parse test.
And was omitting a bypass on the incomplete parsed reply case. Applied
after tweaks.

Amos
--
Please be using
   Current Stable Squid 2.7.STABLE7 or 3.0.STABLE20
   Current Beta Squid 3.1.0.14

Re: [PATCH] Support ICY protocol (ICECast / ICEShout servers) in Squid-3

by Henrik Nordstrom-5 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

mån 2009-11-02 klockan 00:43 +1300 skrev Amos Jeffries:
> Since we have no central place where the headers are upgraded I've had
> to skip porting the upgrade_http0.9 hack in Squid-2 and go straight to
> accepting ICY protocol as an accepted response protocol and handling it.

What do you mean by "no central place"?

Internally the headers should always be upgraded. To implement the
squid-2 option then all that is needed is to detect when the header have
been upgraded and then skip to send the header to the client.

Just remember to not enable chunked encoding for the response..

> Somewhat primitive for now. It's limited to parsing and regenerating the
> status line correctly, and

Do this mean HTTP status lines is also properly preserved now?

> skipping the HTTP/1.0 version override on
> non-HTTP protocol replies.

Right.

What Squid-2 does is to add that header internally with HTTP/0.9 as
version. The version is overridden anyway when the response is composed.

> Since it is on port 80 I've temporarily left the HTTP/1.1 required
> header alterations happening. Some testing will be needed over the next
> few days to ensure that the client software treats unknown headers
> nicely. If needed overrides for those are easily done as well now.

Or fall back on what squid-2 does.. internally upgrade but then skip
sending an HTTP header out..

Regards
Henrik


Re: [PATCH] Support ICY protocol (ICECast / ICEShout servers) in Squid-3

by Amos Jeffries-2 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

On Mon, 02 Nov 2009 22:22:41 +0100, Henrik Nordstrom wrote:
> mån 2009-11-02 klockan 00:43 +1300 skrev Amos Jeffries:
>> Since we have no central place where the headers are upgraded I've had

>> to skip porting the upgrade_http0.9 hack in Squid-2 and go straight to
>> accepting ICY protocol as an accepted response protocol and handling
it.
>
> What do you mean by "no central place"?

The parse+validate and upgrade are spread between HttpStatusLine, HttpMsg,
HttpReply, and client_side.

Testing the first-round patch, found the spot that was triggering the
header addition to 0.9 was done in two places, first by HttpReply on clone
and also by client_side on slow clients after each incomplete status line
parse attempt, then by HttpReply again after the client_side.

It's a little bit of a mess with correctly scoped code and incomplete
conversion of legacy code to such.

>
> Internally the headers should always be upgraded. To implement the
> squid-2 option then all that is needed is to detect when the header have
> been upgraded and then skip to send the header to the client.

This is merely accepting the ICY status line as a valid header. The
sub-headers are still operated as normal. The only crossover between the
protocols seems to be Content-Type which retains semantics. The upgrade is
still done and adds Via, Date, etc normally and seems not to effect the
client software.

I spent a rather enjoyable evening yesterday tested with Nullsoft WinAmp
and VLC against a number of radio and stream servers of various speeds and
networks.

>
> Just remember to not enable chunked encoding for the response..

Oh darn. Guilty.
AFAICT the keep-alive is automatically disabled because there is no
content-length set or possible.

>
>> Somewhat primitive for now. It's limited to parsing and regenerating
the
>> status line correctly, and
>
> Do this mean HTTP status lines is also properly preserved now?

Yes. And non-ICY status lines remains upgraded to HTTP/1.0 as required by
RFC.

>
>> skipping the HTTP/1.0 version override on
>> non-HTTP protocol replies.
>
> Right.
>
> What Squid-2 does is to add that header internally with HTTP/0.9 as
> version. The version is overridden anyway when the response is composed.

Aye. I read the patch many times ;)

Squid-3 stores them as:

 ICY-status
 ICY+HTTP-headers (new HTTP ones last due to appending)

Code which does upgrading on purely version numbers sees ICY as version
0.0. Any which gives trouble (such as chunked encoding) can be modified
slightly to check for sline.protocol==PROTO_HTTP as well as version.

>
>> Since it is on port 80 I've temporarily left the HTTP/1.1 required
>> header alterations happening. Some testing will be needed over the next

>> few days to ensure that the client software treats unknown headers
>> nicely. If needed overrides for those are easily done as well now.
>
> Or fall back on what squid-2 does.. internally upgrade but then skip
> sending an HTTP header out..

Testing succeeded. We don't need to play tricks with the other headers. It
hinges entirely on the first four bytes of the reply being "ICY "

Amos