Linux Media Server

Linux Media Server with selective VPN and Dynamic DNS

Needed a media server for household use with ability to tunnel only SOME traffic through a VPN. Using Ubuntu, Plex Media Server, OpenVPN, iptables packet marking and routing by user.

The machine itself is clearly a home server. Using an old gaming rig with AMD PhenomII x6 Black edition and 8GB ram its plenty of power for what I need. The storage situation is another story entirely. It has 4 HDDs (salvage from donated computers) with Logical Volume Management giving me a grand total of 3TB.

Ubuntu is a great platform for home server use, it’s easy to set up, reliable, and still relatively lightweight. The developers over at have an amazing product worth paying for, flawless organization and collection of my entire media library. With Plex I can access my libraries from anywhere in the world with nearly any platform. The biggest project with this server was selective VPN tunneling – I want some of my traffic encrypted and annonomized, but not necessarily all of it – for daemons running on specific users. The elegant solution was to flag packets from this user and use those flags to change their route. The added benefit of this solution is that by rerouting the packets if my VPN connection fails it also functions as a killswitch.


export INTERFACE="tun0"
export LANIP=""
export NETIF="eth0"
export SCRIPT_NAME="OpenVPN Tunnel Script"

[ "$IFACE" != "lo" ] || exit 0
[ "$IFACE" != "eth0" ] || exit 0

logger -t $SCRIPT_NAME  "Setting up packet marks"

iptables -F -t nat
iptables -F -t mangle
iptables -F -t filter

# mark packets from $VPNUSER

iptables -t mangle -A OUTPUT ! --dest $LANIP  -m owner --uid-owner $VPNUSER -j MARK --set-mark 0x1
iptables -t mangle -A OUTPUT --dest $LANIP -p udp --dport 53 -m owner --uid-owner $VPNUSER -j MARK --set-mark 0x1
iptables -t mangle -A OUTPUT --dest $LANIP -p tcp --dport 53 -m owner --uid-owner $VPNUSER -j MARK --set-mark 0x1
iptables -t mangle -A OUTPUT ! --src $LANIP -j MARK --set-mark 0x1

# allow responses

iptables -A INPUT -i $INTERFACE -m conntrack --ctstate ESTABLISHED -j ACCEPT

# block everything incoming on $INTERFACE


# send DNS to google for $VPNUSER

iptables -t nat -A OUTPUT --dest $LANIP -p udp --dport 53  -m owner --uid-owner $VPNUSER  -j DNAT --to-destination
iptables -t nat -A OUTPUT --dest $LANIP -p tcp --dport 53  -m owner --uid-owner $VPNUSER  -j DNAT --to-destination

# let $VPNUSER access lo and $INTERFACE

iptables -A OUTPUT -o lo -m owner --uid-owner $VPNUSER -j ACCEPT
iptables -A OUTPUT -o $INTERFACE -m owner --uid-owner $VPNUSER -j ACCEPT

# all packets on $INTERFACE needs to be masqueraded


# reject connections from predator ip going over $NETIF

iptables -A OUTPUT ! --src $LANIP -o $NETIF -j REJECT

logger -t $SCRIPT_NAME  "Setting up routes"

GATEWAYIP=`ifconfig $INTERFACE | egrep -o '([0-9]{1,3}\.){3}[0-9]{1,3}' | egrep -v '255|(127\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})' | tail -n1`


if [[ `ip rule list | grep -c 0x1` == 0 ]]; then
    ip rule add from all fwmark 0x1 lookup $VPNUSER
ip route replace default via $GATEWAYIP table $VPNUSER
ip route append default via dev lo table $VPNUSER
ip route flush cache


In addition to this VPN solution I wanted to implement my own Dynamic DNS service for remote access via SSH. To do this I use the APIs from my DreamHost to change DNS entries server side. It’s quick and it’s dirty, but it works.


# dyndns.php takes 2 arguments:

#     host    = the host we're changing, in this case

#     passwd  = key for verification.

#     addr    = optional value of IP we want to use in case I need to change it manually

#     comment = optional comment (shows in DreamHost panel)

RESULT=$(wget -qO- "")

# we need to log everything. We're sysadmins afterall, how else are you supposed to know whats happening?

TIME=$(date +"%r - %D")

echo ${TIME} " - " ${RESULT} >> ${LOGFILE}

and the PHP (acquired from multiple sources and modified to suit)

// set DH_API_KEY to your key (per

// set HOSTS to contain an array of fields that can be modified

// only entries in this list will be updatable



function dh_request($cmd,$aargs=false) {

	$args.="&unique_id=" . uniqid($APP_NAME);
	if(is_array($aargs)) {
		foreach($aargs as $key => $val) {
			$args.="&" . urlencode($key) . "=" . urlencode($val);

	$url=$DH_API_BASE . $args;
	curl_setopt($curl_handle,CURLOPT_URL,$DH_API_BASE . $args);
	$buffer = curl_exec($curl_handle);

	if (empty($buffer)) {
	} else {
function fail($str) {
	printf("error: %s",$str);
function bad_input($e = "") {
	fail("invalid input\n$e\n");
function bad_inputH() {
	fail("host not allowed\n");

//This script can use the detected external IP or one passed through args

if($PASSWD != $passwd) { bad_input("p"); }
if(in_array($host,$HOSTS)) {$host = $host . "";} else { bad_inputH(); }

if(!$DH_API_KEY) {
	if(!$DH_API_KEY) { bad_input(); }

//This is where we get that external IP if necessary
if(!$addr) { $addr=$_SERVER["REMOTE_ADDR"]; }

if($ret["result"] != "success") {
	fail("failed list records\n");
foreach($ret["data"] as $key => $row) {
	if($row["record"] == $host) {
		if($row["editable"] == 0) {
			fail("error: $host not editable");
		if($row["type"] != "A") {
			fail("error: $host not a A record");

if($found) {
	if($addr==$found["value"]) {
		printf("record correct: %s => %s\n", $found["record"], $addr);
		            array("record" => $found["record"],
					      "type" => $found["type"],
						  "value" => $found["value"]));
	if($ret["result"] != "success") {
		fail("failed to remove record\n");
	printf("deleted %s. had value %s\n", $record,$found["value"]);
} else {

                array("record" => $record,
                      "type" => $type,
                      "value" => $addr,
					  "comment" => $comment));

if($ret["result"] != "success") {
	             "failed to add $record of type $type to $addr", $ret["data"]));

printf("success: set %s to %s\n", $record, $addr);

Running this script every 24 hours and after reboots ensures I always have access to my server worldwide.


(505) 450-5744


Rio Rancho, NM 87124
United States of America