Andrei Ostanin

Using OpenVPN for Select Sites With OpenWRT

I love watching Hulu from time to time, but living in Japan means I need to use a proxy or VPN located in the US to view it. I used to use a PPTP VPN server on a VPS to view Hulu, routing all traffic through the VPN. This had some obvious drawbacks in that Hulu playback was slower, the VPN configuration had to be done on each computer, and I couldn’t use things like BitTorrent at the same time as watching videos on Hulu. While this was fine when I’d just watch videos on my laptop once every few weeks, I wanted a more transparent solution once an HTPC and an OpenWRT-capable router entered the equation.

The solution I came up with is simply connecting to an OpenVPN server from my OpenWRT router and selectively routing certain IP addresses through the VPN. The great thing about this setup is that it’s completely transparent for anyone on the LAN. Any computer can stream videos from Hulu without any configuration.

OpenVPN Server

I won’t cover setting up an OpenVPN server in any detail, as that’s already covered in countless other pages on the Internet. I will, however, offer a few hints from my experience setting up an OpenVPN server on FreeBSD.

The VPN doesn’t have to be fast. I used an old FreeBSD server of mine still running in my parents house on a slow cable connection.

I used a combination of these two guides to create a bridged VPN. Here’s my basic OpenVPN config file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
port 1194
proto udp

dev tun

ca /usr/local/etc/openvpn/keys/ca.crt
cert /usr/local/etc/openvpn/keys/example.crt
key /usr/local/etc/openvpn/keys/example.key
dh /usr/local/etc/openvpn/keys/dh1024.pem

server 10.8.0.0 255.255.255.0

duplicate-cn

keepalive 10 120

comp-lzo

persist-key
persist-tun

status /var/log/openvpn/openvpn-status.log

verb 3

I then set up pf using the following pf.conf to NAT the VPN clients and give them access to the Internet:

1
2
3
4
5
ext_if="vr0"
vpn_if="tun0"
vpn_network="10.8.0.0/24"

nat on $ext_if from $vpn_network to any -> ($ext_if)

OpenVPN Client on OpenWRT

I mostly followed the guide here to set up the client. For reference, I listed all the changes to my config files on the router below.

/etc/config/network

1
2
3
4
5
config interface 'vpn'
        option ifname 'tun0'
        option defaultroute '0'
        option peerdns '0'
        option proto 'none'

/etc/config/firewall

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
config zone
        option input 'ACCEPT'
        option forward 'REJECT'
        option output 'ACCEPT'
        option name 'vpn'
        option masq '1'
        option network 'vpn'

config forwarding
        option dest 'lan'
        option src 'vpn'

config forwarding
        option dest 'vpn'
        option src 'lan'

/etc/config/openvpn

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
config openvpn 'example'
        option enabled '1'
        option client '1'
        option dev 'tun'
        option proto 'udp'
        option nobind '1'
        option persist_tun '1'
        option persist_key '1'
        option ca '/etc/openvpn/example.ca'
        option cert '/etc/openvpn/example.cert'
        option key '/etc/openvpn/example.key'
        option comp_lzo '1'
        option verb '3'
        option float '1'
        option remote 'example.us.ostanin.org'
        option client '1'

Selective Routing

Now, we can route IP addresses used by Hulu through the VPN. There are two ways to do this, either have the OpenWRT client call route to add the routes, or have the OpenVPN server push the routes to the client. I choose to push the routes from the server, as this would allow me to connect to the server from outside my LAN and still enjoy Hulu without any additional configuration.

OpenVPN allows you to add routes to the client using the push "route X.X.X.X 255.255.255.255" directive in it’s config file. So we just need to add all the necessary IP addresses for Hulu into the OpenVPN server’s config file and we’re good to go. I wrote a python script to automate the process:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#! /usr/bin/env python

SITES = {
    'hulu': [
        'hulu.com',
        'www.hulu.com',
        'static.hulu.com',
        'ads.hulu.com',
        'assets.hulu.com',
        't2.hulu.com',
        'urlcheck.hulu.com',
        'secure.hulu.com',
        'a.hulu.com',
        'b.hulu.com',
        'c.hulu.com',
        'd.hulu.com',
        'e.hulu.com',
        'f.hulu.com',
        'g.hulu.com',
        'h.hulu.com',
        'i.hulu.com',
        'j.hulu.com',
        'k.hulu.com',
        'l.hulu.com',
        'm.hulu.com',
        'n.hulu.com',
        'o.hulu.com',
        'p.hulu.com',
        'q.hulu.com',
        'r.hulu.com',
        's.hulu.com',
        't.hulu.com',
        'u.hulu.com',
        'v.hulu.com',
        'w.hulu.com',
        'x.hulu.com',
        'y.hulu.com',
        'z.hulu.com',
    ]
}

import socket
import sys

sites = sys.argv[1:]

for site in sites:
    if site in SITES:
        print '###', site
        for host in SITES[site]:
            try:
                addrs = socket.gethostbyname_ex(host)[2]
                print '#', host
                for addr in addrs:
                    print 'push "route', addr, '255.255.255.255"'
            except:
                pass

When run with ./routemaker.py hulu, it will print out a list of all push directives needed to watch Hulu. Here is an example run:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
$ ./routemaker.py hulu
### hulu
# hulu.com
push "route 60.254.153.10 255.255.255.255"
push "route 60.254.153.25 255.255.255.255"
# www.hulu.com
push "route 210.149.135.22 255.255.255.255"
push "route 210.149.135.45 255.255.255.255"
# static.hulu.com
push "route 23.3.104.33 255.255.255.255"
push "route 23.3.104.8 255.255.255.255"
# ads.hulu.com
push "route 23.3.104.65 255.255.255.255"
push "route 23.3.104.59 255.255.255.255"
# assets.hulu.com
push "route 23.3.104.42 255.255.255.255"
push "route 23.3.104.75 255.255.255.255"
# t2.hulu.com
push "route 23.3.104.32 255.255.255.255"
push "route 23.3.104.9 255.255.255.255"
# urlcheck.hulu.com
push "route 208.91.157.69 255.255.255.255"
# secure.hulu.com
push "route 118.215.182.31 255.255.255.255"
# a.hulu.com
push "route 23.3.104.59 255.255.255.255"
push "route 23.3.104.65 255.255.255.255"
# c.hulu.com
push "route 210.149.135.21 255.255.255.255"
push "route 210.149.135.14 255.255.255.255"
# g.hulu.com
push "route 208.91.157.28 255.255.255.255"
# i.hulu.com
push "route 208.91.157.16 255.255.255.255"
# m.hulu.com
push "route 23.3.104.66 255.255.255.255"
push "route 23.3.104.56 255.255.255.255"
# p.hulu.com
push "route 23.3.104.43 255.255.255.255"
push "route 23.3.104.81 255.255.255.255"
# r.hulu.com
push "route 210.149.135.29 255.255.255.255"
push "route 210.149.135.44 255.255.255.255"
# s.hulu.com
push "route 23.3.104.65 255.255.255.255"
push "route 23.3.104.33 255.255.255.255"
# t.hulu.com
push "route 208.91.157.68 255.255.255.255"
# u.hulu.com
push "route 208.91.157.20 255.255.255.255"

You can now copy and paste that to your OpenVPN server config and restart the server. You could also automate it to run once a day and restart OpenVPN automatically if you so wish.

If everything worked, the Hulu routes should have been added to your client:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
root@router:~# route
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
default         182.236.18.15   0.0.0.0         UG    0      0        0 pppoe-wan
10.8.0.1        10.8.0.5        255.255.255.255 UGH   0      0        0 tun0
10.8.0.5        *               255.255.255.255 UH    0      0        0 tun0
23.3.104.8      10.8.0.5        255.255.255.255 UGH   0      0        0 tun0
23.3.104.9      10.8.0.5        255.255.255.255 UGH   0      0        0 tun0
23.3.104.32     10.8.0.5        255.255.255.255 UGH   0      0        0 tun0
23.3.104.33     10.8.0.5        255.255.255.255 UGH   0      0        0 tun0
23.3.104.34     10.8.0.5        255.255.255.255 UGH   0      0        0 tun0
23.3.104.42     10.8.0.5        255.255.255.255 UGH   0      0        0 tun0
23.3.104.43     10.8.0.5        255.255.255.255 UGH   0      0        0 tun0
23.3.104.56     10.8.0.5        255.255.255.255 UGH   0      0        0 tun0
23.3.104.59     10.8.0.5        255.255.255.255 UGH   0      0        0 tun0
23.3.104.65     10.8.0.5        255.255.255.255 UGH   0      0        0 tun0
23.3.104.66     10.8.0.5        255.255.255.255 UGH   0      0        0 tun0
23.3.104.73     10.8.0.5        255.255.255.255 UGH   0      0        0 tun0
23.3.104.75     10.8.0.5        255.255.255.255 UGH   0      0        0 tun0
23.3.104.81     10.8.0.5        255.255.255.255 UGH   0      0        0 tun0
23.48.134.31    10.8.0.5        255.255.255.255 UGH   0      0        0 tun0
60.254.153.10   10.8.0.5        255.255.255.255 UGH   0      0        0 tun0
60.254.153.25   10.8.0.5        255.255.255.255 UGH   0      0        0 tun0
182.236.18.15   *               255.255.255.255 UH    0      0        0 pppoe-wan
192.168.1.0     *               255.255.255.0   U     0      0        0 br-lan
208.91.157.16   10.8.0.5        255.255.255.255 UGH   0      0        0 tun0
208.91.157.20   10.8.0.5        255.255.255.255 UGH   0      0        0 tun0
208.91.157.28   10.8.0.5        255.255.255.255 UGH   0      0        0 tun0
208.91.157.68   10.8.0.5        255.255.255.255 UGH   0      0        0 tun0
208.91.157.69   10.8.0.5        255.255.255.255 UGH   0      0        0 tun0
210.149.135.30  10.8.0.5        255.255.255.255 UGH   0      0        0 tun0
210.149.135.53  10.8.0.5        255.255.255.255 UGH   0      0        0 tun0

And there you have it! Hulu should now work on all devices within the LAN. You can make other sites work too by adding their hostnames to the script as well. Keep in mind that ads from Hulu will be sent through the VPN while videos themselves will not, resulting in very laggy ads if your VPN is slow like mine. If your Hulu client allows choosing a quality setting for ads (like the XBMC client does), set them to the lowest quality.

Backing Up Simple Websites With Git

I’m hosting three different WordPress blogs for different people (this one included) and recently decided to switch away from my home server to a VPS. My home server has a RAID-Z array and important data is backed up remotely on a regular basis giving me the peace of mind that my data is safe, but I can’t be so sure about the VPS. Also, I might switch to a different VPS provider in the future and wanted to make deploying the blogs as easy as possible. I came up with the following solution.

Goals

  • Daily backups usable for small websites with little traffic
  • Backups should be complete: all files, MySQL dump, logs
  • Flexibility

Why Git?

Using git has many inherent advantages over simply copying files to a remote server.

  • Daily backups will only store diffs, taking up little space
  • Using a VCS allows you to see history, merge fixes, branch, etc. if you need to (eg. for developing plugins/themes on one computer and easily merging them into your website)
  • Push backups to a remote server using http, https, ssh, or git protocols
  • Easily exclude files from backups using .gitignore
  • Setting up a new server is just a git clone away
  • Know your backup succeeded just by browsing your git repository
  • More!

So, why should we store the database backup and log files in git as well?

  • Complete history of your website – restore the files AND database to any state you want
  • The diffs in each commit give you the traffic for the day and which rows were added to your database
  • No need for separate backup methods!

Of course, git isn’t the perfect solution for all websites. High-traffic websites may find their git repositories quickly grow in size when storing database backups and log files.

Structure

To make things easier, I structured each of the sites in the following fashion:

1
2
3
4
5
6
7
8
/usr/local/www/
`- example.com/
   |- logs/
   |  `- example.com-access.log
   |  `- example.com-error.log
   |- root/ # WordPress installation
   `- sql/
      `- example.com.sql # MySQL dump

Also, the MySQL username and database name for each site is the same as the URL, so “example.com” for this case.

Prepare the Remote Repository on the Backup Server

Create an empty repository on the backup server.

1
2
3
4
$ cd /path/to/git/repositories
$ mkdir example.com.git
$ cd example.com.git
$ git init --bare --shared

Add data to the repository

We can clone the empty repository we just made and add the initial data to it. Do this on the server your site is currently hosted on.

1
2
3
4
5
6
7
$ git clone http://git.example.com/example.com
$ cd example.com
$ mkdir logs sql
$ touch logs/example.com-access.log logs/example.com-error.log
$ mysqldump -uexample.com -pPASSWORD --skip-extended-insert --skip-comments example.com > sql/example.com.sql
# Copy the WordPress install
$ cp -r /path/to/site/root root

I also made a simple Makefile to automate backing up. Edit it to suit your needs (especially the MYSQL_ variables) and place it in the root of your repository.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
SITE_NAME=  $(notdir $(CURDIR))
MYSQL_USER= $(SITE_NAME)
MYSQL_PASS= PASSWORD
MYSQL_DB=   $(SITE_NAME)

.PHONY: backup
backup: sql_backup git_commit git_push
  @echo Backup of $(SITE_NAME) finished successfully!

.PHONY: sql_backup
sql_backup:
  @echo Backing up the database
  @mysqldump -u$(MYSQL_USER) -p$(MYSQL_PASS) --skip-extended-insert --skip-comments $(MYSQL_DB) > sql/$(SITE_NAME).sql

.PHONY: git_commit
git_commit: git_commit_sql git_commit_logs git_commit_root

.PHONY: git_commit_logs
git_commit_logs:
  @echo Committing log files
  @-git commit logs -m "Updating logs"

.PHONY: git_commit_root
git_commit_root:
  @echo Committing root
  @-git add root
  @-git commit root -m "Updating root"

.PHONY: git_commit_sql
git_commit_sql:
  @echo Committing sql
  @-git commit sql -m "Updating sql dump"

.PHONY: git_push
git_push:
  @echo Pushing commit
  @-git push

Now we can commit changes and push to our backup server. Remember that you can add a .gitignore to keep files or directories from being backed up.

1
2
3
$ git add .
$ git commit -m "initial commit"
$ git push origin master

Deploying

Now when you want to deploy your site, set up the MySQL user and database then checkout the site from git.

1
2
3
4
$ cd /usr/local/www
$ git clone --shared http://git.example.com/example.com
$ cd example.com
$ mysql -uexample.com -pPASSWORD example.com < sql/example.com.sql

As long as your web server points to /usr/local/www/example.com, you should now be able to access your site!

If you want to test backups, just generate some log activity or do something which would update the database and run make backup from /usr/local/www/example.com.

Automating Backups

You can automate backups by using cron.

Access your crontab

1
$ crontab -e

…and add the following line:

1
@daily cd /usr/local/www/example.com && make backup &> /dev/null

If all goes well, you should see daily commits popping up on your backup server! If you ever lose data, you can go back to the deploying section and go back to your last backup.

Managing Config Files With Git

Keeping config files synchronized across multiple computers can be a pain. Simply putting config files into a git repository and cloning it on all your computers still requires you to make symlinks to your config files. I wanted an easier solution, so I wrote some Makefiles to make symlinking new files almost automatic.

Goals:

  • Config files are stored in subdirectories at ~/config
  • Each subdirectory has rules on where to symlink the config files (eg. ~/config/home makes symlinks in ~)
  • Subdirectories are handled automatically (eg. ~/config/home/.ssh/config makes a symlink in ~/.ssh/config)
  • Symlinks are updated by running make install in directories with Makefiles
  • Support for both GNU Make and BSD Make

Set up the repository on the server:

1
2
3
4
$ cd /path/to/git/repositories
$ mkdir config.git
$ cd config.git
$ git init --bare

Clone the repository to a machine and set it up:

1
2
3
4
5
6
7
8
9
10
11
12
# Substitute the location of your git repository
$ git clone http://git.example.com/config
$ cd config
# Make a directory to store config files from ~
$ mkdir home
$ cd home
# Add some files
$ cp ~/.gitconfig .
$ cp ~/.tmux.conf .
$ cp ~/.vimrc .
$ mkdir .ssh
$ cp ~/.ssh/config .ssh

Now, add the Makefiles. Here is config/config.mk:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
GIT_LS_COMMAND= git ls-files | grep -v -e Makefile
GIT_FILES=      $(shell $(GIT_LS_COMMAND))   # GNU Make compatible
GIT_FILES!=     $(GIT_LS_COMMAND)            # BSD Make compatible

FILES?=         $(GIT_FILES)
CURDIR?=        $(.CURDIR)                   # BSD Make compatibility

COMMAND?=       ln -sf
DEST?=          $(HOME)

.PHONY: all
all:
  @echo Usage:
  @echo make install - Run default command set in the Makefile \(default is to symlink\)

.PHONY: install $(FILES)
install: $(FILES)

$(FILES):
  @echo $(DEST)/$@
  @if test ! -d `dirname "$(DEST)/$@"`; then \
    mkdir -p `dirname "$(DEST)/$@"`; \
  fi
  @if test -e "$(DEST)/$@"; then \
    rm "$(DEST)/$@"; \
  fi
  @$(COMMAND) "$(CURDIR)/$@" "$(DEST)/$@"

And config/home/Makefile:

1
2
3
4
COMMAND= ln -sf
DEST=    $(HOME)

include ../config.mk

The makefile only tracks files which are added to git, so make sure to add and commit all the config files you want to track. You can add more directories to the repository and make a Makefile for each. By editing DEST variable in the Makefile, you can set the destination directory, and by editing COMMAND to cp for instance, you can copy files instead of symlinking.

To install the config files on another computer after committing and pushing the repository:

1
2
3
4
# Substitute the location of your git repository
$ git clone http://git.example.com/config
$ cd config/home
$ make install

Easy as that! Hope this helps someone.

Sending Large Strings of Data to Arduino

For one of my projects I needed to send between 200 and 500 bytes of data to my Arduino quickly over a serial connection. The problem I faced is that the Arduino only has a 128 byte read buffer. Sending strings of data longer than 128 bytes would cause unpredictable results, especially at higher speeds. Sometimes the Arduino would receive all the data successfully and sometimes it would fail.

Specifically, my project involved sending commands to the Arduino with the following requirements:

  • All commands are simple ASCII strings, terminated with a newline character
  • Most commands are small, only 1~2 characters in length
  • Few commands are over 128 characters in length, causing the Arduino’s receive buffer to overflow
  • All commands have to be sent to the Arduino with absolutely no loss of data
  • Latency kept to a minimal
  • When the Arduino detects a serial link, it sends OK which the host checks to make sure the serial link is usable

I met the above requirements by creating a custom protocol for sending larger commands:

  • All small commands are read as usual, introducing no unneeded latency
  • If a command starts with RCV, then the custom protocol is invoked to read 128 bytes at a time until the full command is received

Perhaps the best way to illustrate the custom protocol is with an example of sending 200 bytes of ‘a’s to the Arduino:

1
2
3
4
5
6
7
8
9
10
11
12
# The host initiates a new command 200 bytes in length
host> RCV 200
# The Arduino states that it is ready to receive the data
arduino> RDY
# The host sends the first 128 bytes
host> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
# The Arduino acknowledges that it successfully received 128 bytes of data
arduino> ACK 128
# The host sends the remaining 72 bytes:
host> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
# The Arduino acknowledges the 72 bytes of data, and the transfer is completed
arduino> ACK 72

I created a simple Arduino test program which can receive any string up to 1024 bytes in size and echoes it back over the serial connection:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <stdio.h>

void setup() {
	Serial.begin(115200);

	Serial.println("OK");
}

char command[1024];
char commandBuffer[128];
int commandBufferSize = 0;

void readCommandBuffer(int bytesToRead) {
	int i = 0;
	char c = 0;
	while (i < 128 && (i < bytesToRead || bytesToRead <= 0)) {
		while (!Serial.available())
			;
		c = Serial.read();
		if (c == '\r' || c == '\n') {
			break;
		}
		commandBuffer[i] = c;
		i++;
	}
	commandBufferSize = i;
}

void readCommand() {
	command[0] = '\0';
	readCommandBuffer(0);
	if (strncmp(commandBuffer, "RCV", 3) == 0) {
		commandBuffer[commandBufferSize] = '\0';
		int expectedSize = atoi(commandBuffer + 4);
		if (expectedSize <= 0 || expectedSize > 1024) {
			return;
		}
		Serial.println("RDY");
		int bytesRead = 0;
		while (bytesRead < expectedSize) {
			readCommandBuffer(expectedSize - bytesRead);
			memcpy(command + bytesRead, commandBuffer, commandBufferSize);
			bytesRead += commandBufferSize;
			Serial.print("ACK ");
			Serial.println(commandBufferSize);
		}
		command[bytesRead] = '\0';
	} else {
		memcpy(command, commandBuffer, commandBufferSize);
		command[commandBufferSize] = '\0';
	}
}

void loop() {
	if (Serial.available()) {
		readCommand();
		// "command" now contains the full command
		Serial.println(command);
	}

To test the stability of this implementation, I also created a Python script which creates a random string between 1 and 1023 characters in length, sends it to the Arduino, and verifies that it was sent correctly by comparing the string echoed by the Arduino to the original string. It repeats 1000 times and prints the amount of successes and failures at the end. You’ll need pySerial for this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import math

import serial
from threading import Thread, Lock
class Serial:
	def __init__(self, port='/dev/cuaU0', rate=9600, timeout=10):
		self._serial = serial.Serial(port, rate, timeout=timeout)
		self._mutex = Lock()
		self._mutex.acquire()
		response = self._serial.readline().strip()
		if response != 'OK':
			raise Exception("Failed to communicate with the serial device!")
		self._mutex.release()

	def _shortCommand(self, command):
		self._serial.write(command)
		response = self._serial.readline()
		return response.strip()

	def _longCommand(self, command):
		response = self._shortCommand('RCV ' + str(len(command)) + "\n")
		if response != 'RDY':
			return None
		for i in range(int(math.ceil(len(command) / 128.0))):
			c = command[128*i:128*(i+1)]
			response = self._shortCommand(c)
		return self._serial.readline().strip()

	def command(self, command):
		self._mutex.acquire()
		if len(command) < 128:
			response = self._shortCommand(command + "\n")
		else:
			response = self._longCommand(command)
		self._mutex.release()
		return response

import random, string
def main():
	serial = Serial(port='/dev/cu.usbserial-A6008ikf', rate=115200)
	passed = 0
	failed = 0
	for i in range(1000):
		l = random.randint(1, 1023)
		s = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for x in range(l))
		o = serial.command(s)
		if o == s:
			print (i + 1), 'OK', l, 'bytes'
			passed = passed + 1
		else:
			print (i + 1), 'FAIL', l, 'bytes'
			failed = failed + 1
	print 'Passed:', passed
	print 'Failed:', failed

if __name__ == '__main__':
	main()

I found that the implementation was completely stable even at 115200 baud. The Serial class from the above snippet should even work in a multithreaded Python script with no problems. It’s the same code I’m using in my project.

Hope this is of some use to someone!

Root on Zfs With Freebsd 9

There are many guides for installing FreeBSD on a ZFS partition floating around on the web, however most of them were outdated or just didn’t quite meet my needs so I used them as a basis to write a new guide.

What makes this guide unique is that FreeBSD is installed inside of a filesystem on the zpool allowing for much more flexibility compared to installing directory to the root of the zpool. For instance, you can make a snapshot of the whole installation with one command, update the kernel and userland, and if for some reason something goes wrong, just revert to the old system with a single command!

Also, swap is kept on the ZFS partition as well, allowing you to easily resize or add swap space as needed.

You will need either the disk1.iso or memstick.img image of FreeBSD for this guide. I tested this guide with FreeBSD 9.0-rc3 but it should work with future versions as well.

Boot from the install disk and choose the “Live CD” option. Login as root.

This guide assumes your root disk is ada0, replace it with your real root disk.

First, we partition the disk. Repeat this process with another disk if you want to use a mirrored root partition.

1
2
3
4
5
6
7
8
# Create a GUID partition table on the disk
$ gpart create -s gpt ada0
# Add the boot partition
$ gpart add -s 64k -t freebsd-boot ada0
# Add the ZFS partition and label it as "root"
$ gpart add -t freebsd-zfs -l root ada0
# Write the boot loader to the disk
$ gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 ada0

If the first command gives you a gpart: geom 'ada0': File exists error, you need to erase the existing using dd if=/dev/zero of=/dev/ada0 bs=512 count=1.

Now we can create the zpool on the disk.

1
2
3
4
5
6
# Create the pool using the "root" label
# Note: For a mirror use zpool create zroot mirror /dev/gpt/root0 /dev/gpt/root1
$ zpool create zroot /dev/gpt/root
# Export and reimport the pool storing the cache in /tmp
$ zpool export zroot
$ zpool import -o cachefile=/tmp/zpool.cache zroot

Mount and set up the filesystems on the ZFS partition. These are just examples so modify them for your system.

1
2
3
4
5
6
7
8
9
10
$ zfs set mountpoint=none zroot
$ zfs create -o mountpoint=/mnt zroot/freebsd
$ zfs create zroot/freebsd/usr
$ zfs create zroot/freebsd/usr/home
$ zfs create zroot/freebsd/var
$ zfs create -o compression=on -o setuid=off zroot/freebsd/usr/ports
$ zfs create -o compression=off -o exec=off -o setuid=off zroot/freebsd/usr/ports/distfiles
$ zfs create -o compression=on zroot/freebsd/usr/src
$ zfs create -o compression=on -o exec=on -o setuid=off zroot/freebsd/tmp
$ zfs create -o compression=on -o exec=on -o setuid=off zroot/freebsd/var/tmp

Create a ZFS volume to use as swap.

1
2
3
$ zfs create -V 4G zroot/swap
$ zfs set org.freebsd:swap=on zroot/swap
$ zfs set checksum=off zroot/swap

Set the proper permissions on the tmp directories and add a symlink to the home directory.

1
2
$ chmod 1777 /mnt/tmp /mnt/var/tmp
$ cd /mnt; ln -s usr/home home; cd -

Set the boot volume.

1
$ zpool set bootfs=zroot/freebsd zroot

Now we can install the FreeBSD base system onto our ZFS partition. You can customize which sets you want to install. This may take a while to complete.

1
2
3
4
5
$ sh
$ cd /usr/freebsd-dist
$ export DESTDIR=/mnt
$ for file in base.txz doc.txz kernel.txz lib32.txz ports.txz src.txz;
$ > do (cat $file | tar --unlink -xpJf - -C ${DESTDIR:-/}); done

Copy the zpool’s cache file to the new installation.

1
$ cp /tmp/zpool.cache /mnt/boot/zfs/zpool.cache

Enable ZFS to load on boot.

1
2
3
$ echo zfs_load=\"YES\" >> /mnt/boot/loader.conf
$ echo vfs.root.mountfrom=\"zfs:zroot/freebsd\" >> /mnt/boot/loader.conf
$ echo zfs_enable=\"YES\" >> /mnt/etc/rc.conf

FreeBSD will complain about a missing fstab, so make sure to create an empty one.

1
$ touch /mnt/etc/fstab

Almost done! Now we need to unmount the ZFS filesystems and set the proper mount points.

1
2
3
4
5
$ zfs umount -a
$ zfs set mountpoint=legacy zroot/freebsd
$ zfs set mountpoint=/tmp zroot/freebsd/tmp
$ zfs set mountpoint=/usr zroot/freebsd/usr
$ zfs set mountpoint=/var zroot/freebsd/var

Now reboot into your new FreeBSD install!

1
$ shutdown -r now

Once the system is back up, login as root and finish the installation by setting the root password and timezone.

1
2
$ passwd
$ tzsetup