For some time I’d been noticing that Netflix would fail to stream in HD at peak times. The rated bandwidth of my connection has plenty of headroom for streaming, and other services wouldn’t show the same problem. After lobbing complaints at both Comcast and Netflix to no avail (Netflix did send a very polite letter back asking if I’d tried turning it off and back on again), and based on a suggestion by Gigaom that Verizon was underprovisioning peering connections that it knew would carry Netflix traffic, I decided to investigate the possibility that some link carrying Netflix streams towards Comcast subscribers was getting saturated at peak times.

I have a Linux VPS with modest CPU and RAM but plenty of bandwidth relative to the currently “suspended” Comcast monthly cap, even considering that proxied data counts double. So I tried installing¬†tinyproxy on the VPS and redirecting HTTP traffic from my Roku through the proxy. I run OpenWRT on my home router so I used the following OpenWrt firewall rule:

config redirect
        option target 'DNAT'
        option src 'lan'
        option src_ip '<Roku IP>'
        option src_dport 80
        option dest_ip '<VPS IP>'
        option dest_port 8888
        option proto tcp

Using iptables, something like this should work:

-s <Roku IP>/32 -p tcp -m tcp --dport 80 -j DNAT --to-destination <VPS IP>:8888

Initially, it did not work very well. Then I tried making the following change to tinyproxy to increase the socket buffers:

diff -ur tinyproxy-1.8.2.orig/src/child.c tinyproxy-1.8.2/src/child.c
--- tinyproxy-1.8.2.orig/src/child.c    2013-12-01 12:36:18.000000000 -0800
+++ tinyproxy-1.8.2/src/child.c 2013-11-02 10:41:47.234673559 -0700
@@ -188,6 +188,7 @@
 static void child_main (struct child_s *ptr)
         int connfd;
+        int sndbuf;
         struct sockaddr *cliaddr;
         socklen_t clilen;

@@ -208,6 +209,12 @@

                 connfd = accept (listenfd, cliaddr, &clilen);

+                sndbuf = 200000;
+                if (setsockopt (connfd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof (sndbuf)) == -1) {
+                        log_message (LOG_WARNING,
+                                     "setsockopt failed: %s\n",
+                                     strerror (errno));
+                }
 #ifndef NDEBUG 
                  * Enable the TINYPROXY_DEBUG environment variable if you
diff -ur tinyproxy-1.8.2.orig/src/sock.c tinyproxy-1.8.2/src/sock.c
--- tinyproxy-1.8.2.orig/src/sock.c     2010-03-09 03:38:37.000000000 -0800
+++ tinyproxy-1.8.2/src/sock.c  2013-11-02 10:16:52.114662904 -0700
@@ -78,6 +78,7 @@
 int opensock (const char *host, int port, const char *bind_to)
         int sockfd, n;
+        int rcvbuf;
         struct addrinfo hints, *res, *ressave;
         char portstr[6];

@@ -133,6 +134,13 @@
                 return -1;

+        rcvbuf = 200000;
+        if (setsockopt (sockfd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof (rcvbuf)) == -1) {
+               log_message (LOG_WARNING,
+                            "setsockopt failed: %s\n",
+                            strerror (errno));
+        }
         return sockfd;

Since increasing the socket buffers it has worked great. No trouble streaming HD and the buffering meter moves like a hot knife through butter.

Before trying the socket buffers, I tried redirecting DNS through the proxy as well, but that doesn’t seem to be necessary.

After identifying the socket buffer issue, I considered the possibility that there is some peculiarity of the streaming client in the 1st-gen Roku that makes it more sensitive to congestion than other clients. (One difference, I gather, is that the 1st-gen Roku doesn’t do adaptive streaming — but that doesn’t explain why HD streaming doesn’t work, it just makes it less obvious when the stream underruns.) But given the widely-known tensions between Netflix and large ISPs that have video offerings of their own, I’m not planning to invest time or money trying to find a technical explanation for what is likely a business problem.