Jenny (wife) and I were hanging out a lot in our living room after Max (baby) was born. This led to a lot of “inconvenient” music playing, either by running an audio cable across the room to a laptop or plugging in one of our phones to the receiver. After getting input from the binary brain trust, I ended up trying out Logitech Media Server (nee Slimbox Server) on my Ubuntu machine. It worked out of the box with my odd FLAC and cuesheet rips and the Debian package seemed pretty good, so I ended up buying a Squeezebox Touch. The Touch was simple to install, and I was happy with my new stereo setup.

At least, I was happy until I got to work and tried connecting home. I had previously done a quick Google search for Squeezebox and SSH tunneling and the Logitech Squeezebox “Connecting Remotely” wiki indicated that the only required port was 9000. A little weird, as port 3483 was mentioned earlier in the article, but oh well. I had figured the other Google results were people struggling to figure out SSH tunnels, but I was wrong; most of the other relevant results were users describing their SSH failures on the Squeezebox forum.

As a late-comer to the Squeezebox game, I had some catching up to do. From the best I can tell, it appears that the desktop application SqueezePlay had succeeded the previous official players, and it mimicked the Squeezebox Touch’s interface. This was a good thing, as SqueezePlay’s UI felt solid and robust, whereas the others felt rickety and dated. SqueezePlay also introduced a new requirement: it requires UDP traffic over port 3483. It appears that previous clients only needed TCP traffic over 3483 and 9000. The majority of this information was derived from Squeezeplay desktop over SSH tunnel - possible?, Can’t Access Server over SSH!, and Problem communicating across networks using SSH tunnel.

The nugget that pushed me forward came from welom when linked to Performing UDP tunneling through an SSH connection in a post. Although I’m well trained with SSH and port forwarding, I had never needed to differentiate between UDP and TCP before. Some serverfault.com responses later, combined with previous link, left me understanding that SSH didn’t do UDP traffic, but that there was probably some way around it. I pursued the shorter solution with socat first, which I found out let you easily capture and redirect traffic, including translating packets between TCP and UDP. It got even better when I found out there was a socat package for homebrew. Some bash scripting commenced and boom! SqueezePlay connected!

#!/bin/bash

username=example_user
hostname=example.com

ssh -f -g \
  -L 3483:localhost:3483 \
  -L 3484:localhost:3484 \
  -L 9000:localhost:9000 \
  $username@$hostname \
  'socat tcp4-listen:3484,reuseaddr,fork UDP:localhost:3483'

socat -T15 udp4-recvfrom:3483,reuseaddr,fork tcp:localhost:3484

To connect, I needed to open up three ports: the expected 3483 and 9000 for TCP traffic, and an additional arbitrary port (I used 3484) for transferring the UDP traffic. On the client-side, socat is used to capture any UDP traffic received on 3483 (which SqueezePlay uses to find clients) and redirects it as TCP traffic to port 3484. With the SSH tunnel listening on port 3484, that traffic then gets transferred to the server. A socat instance running on the server then listens for any TCP packets on port 3484 and translates it back to UDP on port 3484. The parameters for socat were all found in the UDP SSH article and look “helpful” when checked against socat’s man, so I’ve kept them in.

It’s worth bringing up ssh’s -g option, which allows remote hosts to connect to local forwarded ports. This will allow anyone to see your media server on your client’s network. It is required because SqueezePlay and the Squeezebox Touch attempt to find a media server by broadcasting a search request to your entire network. By default, your ssh connection will not respond to the broadcast because the relayed broadcast isn’t originating from your localhost. If security is a concern, you might look into replacing the -g option with specific bind addresses for your forwarded ports (for example, -L 192.168.1.XXX:3483:localhost:3483). You only need to specify bind addresses for the TCP ports (3483 and 9000), as the UDP port is being handled by socat.

I’ve extended the bash script to avoid having to manually kill processes when you are done. It’s working well under OS X Lion, but I won’t guarantee it working cross-platform. For that matter, I wouldn’t even guarantee it’s well written, as I’ve used this as a big Bash learning experience.

#!/bin/bash
set -e

USERNAME=example_user
HOSTNAME=example.com

trap endConnections SIGTERM SIGINT
endConnections() {
  for PID in $SSH_PID $SOCAT_PID; do
    if kill -s 0 $PID; then
      kill $PID
    fi
  done
}

echo "Opening SSH localhost"

ssh -tt -f -g \
  -o 'ExitOnForwardFailure yes' \
  -L 3483:localhost:3483 \
  -L 3484:localhost:3484 \
  -L 9000:localhost:9000 \
  -l $USERNAME \
  $HOSTNAME \
  'socat tcp-listen:3484,reuseaddr,fork UDP:localhost:3483'

SSH_PID=$(ps -ef | grep ssh.*$HOSTNAME.*socat | grep -v grep | sed 's/^\s*//' | awk '{print $2}')

# My terminal input got messed up from passing -tt and -f to ssh.
# The quickest fix is to run reset.
reset

echo "Forwarding UDP traffic"
socat -T15 udp-recvfrom:3483,reuseaddr,fork TCP:localhost:3484 &
SOCAT_PID=$!

echo "Forwarding complete. If you like, you can background the script at this point."
wait

endConnections

Good luck!