Installing a DragonFly BSD Jail

I’m installing a jail on a freshly upgraded DragonFly BSD 2.13-DEVELOPMENT box. There’s instructions in the DragonFly manual, and on the Web site. They’re fine as far as they go, but to make the jail truly useful you need to do a little more.

Before starting, decide some important facts about your jail.

  • Root directory for the jail filesystem
  • IP address used by the jail
  • hostname of your jail
  • My jail hostname will be mwltest4, on the IP 192.0.2.9, in the directory /jail/mwltest4.

    A jail requires exclusive use of a single IP address. That IP must be bound to the server as an alias. Make an appropriate alias entry in /etc/rc.conf. Note that an alias needs an all-ones netmask. While we’re there, enable jails and tell the host server that we’re building the jail mwltest4.

    ifconfig_em0_alias0="inet 192.0.2.9 netmask 255.255.255.255"
    jail_enable="YES"
    jail_list="mwltest4"

    rc.conf also needs entries for each jail, so that the various jail management utilities can find and configure the jail.

    jail_mwltest4_rootdir="/jail/mwltest4"
    jail_mwltest4_hostname="mwltest4"
    jail_mwltest4_ip="192.0.2.9"

    Start by seeing what network ports your server listens on. I’ve removed all of the entries with remote addresses, because those are live network sessions; I’m only interested in what ports the server is listening on.

    # sockstat -4
    USER COMMAND PID FD PROTO LOCAL ADDRESS FOREIGN ADDRESS
    ...
    root sendmail 670 4 tcp4 127.0.0.1:25 *:*
    root sshd 656 5 tcp4 *:22 *:*

    Any entry where the local address is an asterisk followed by a colon and a port number will be a problem. We need to bind those daemons to the server’s main IP address. In this example, the only problem daemon is SSH. Bind SSH to a single IP address with a ListenAddress directive in /etc/ssh/sshd_config.

    ListenAddress 192.0.2.8

    Run /etc/rc.d/sshd restart, and sshd will bind only to the specified IP.

    I want my jails on their own filesystem, so I create a new HAMMER PFS and a directory for this particular jail.

    # hammer pfs-master /jail
    Creating PFS #9 succeeded!
    /jail
    sync-beg-tid=0x0000000000000001
    sync-end-tid=0x00000001068ea510
    shared-uuid=34cc9fbe-ffc2-11e0-9527-010c29ce51d2
    unique-uuid=34cc9fdd-ffc2-11e0-9527-010c29ce51d2
    label=""
    prune-min=00:00:00
    operating as a MASTER
    snapshots directory defaults to /var/hammer/

    # mkdir /jail/mwltest4

    Now install the userland, exactly as per the jail instructions.

    # setenv D /jail/mwltest4
    # cd /usr/src/
    # make installworld DESTDIR=$D

    Go get more caffiene. By the time you return you should see:

    ===> etc
    ===> etc/sendmail
    install -o root -g wheel -m 644 /usr/src/Makefile_upgrade.inc /jail/mwltest4/etc/upgrade/
    #

    It finished successfully. Now install /etc.

    # cd etc/
    # make distribution DESTDIR=$D -DNO_MAKEDEV_RUN

    Now mount a device filesystem for the jail.

    # cd $D
    # ln -sf dev/null kernel
    # mount_devfs $D/dev

    Edit /etc/fstab to have the host mount the jail devfs whenever the system starts.

    devfs /jail/mwltest4/dev devfs rw 0 0

    Our jail should be ready. Start it in single-user mode.

    # jail /jail/mwltest4/ mwltest4 127.0.0.1,192.0.2.9 /bin/sh
    # uname -a
    DragonFly mwltest4 2.13-DEVELOPMENT DragonFly v2.13.0.49.gf6ce8-DEVELOPMENT #0: Tue Oct 18 10:51:40 EDT 2011 mwlucas@mwltest2.blackhelicopters.org:/usr/obj/usr/src/sys/GENERIC i386
    #

    Before starting your jail in multiuser mode

  • enable SSH
  • configure /etc/resolv.conf
  • set a root password
  • and add a user
  • As I use LDAP for central account administration, but the jail isn’t yet LDAPilated, I manually set my new user ID to be identical to that on the host, and I add that account to the wheel group. Also modify /etc/ssh/sshd_config to listen only to the jail’s IP address. (While this isn’t strictly necessary, it will simplify managing the host server.)

    On the host, with my unprivileged account, I run:

    $ cp -rp .ssh /jail/mwltest4/usr/home/mwlucas/
    $ cp .cshrc /jail/mwltest4/usr/home/mwlucas/

    My jail account now has my authorized_keys file and my SSH configuration, with correct permissions, along with my preferred shell environment.

    Start the jail in multiuser mode:

    # /etc/rc.d/jail start mwltest4
    Configuring jails:.
    Starting jails: mwltest4.
    #

    I can now SSH to the jail, become root, and install pkgsrc.

    # cd /usr/src
    # make pkgsrc-create
    If problems occur you may have to rm -rf pkgsrc and try again.

    mkdir -p /usr/pkgsrc
    cd /usr/pkgsrc && git init
    git: not found
    *** Error code 127

    Stop in /usr.

    Crap. The DragonFly install installs git via package as part of the OS install. git is used for installing pkgsrc. You use pkgsrc to install git. How can we bootstrap git? pkg_radd lets you install remote packages, but it is built on pkg_add, part of pkgsrc.

    Find a FTP server (or mount an ISO) with the version of the scmgit package that runs on your host server. I would up getting the scmgit-base-1.7.4.1 package from the 2011Q1 pkgsrc. This is the same package that was originally installed on my DragonFly machine, and it still runs on the DragonFly installed on this host, so it should be okay.

    # pkg_add -f -P /jail/mwltest4/ ftp://ftp.allbsd.org/pub/DragonFly/packages/i386/DragonFly-2.10/pkgsrc-2011Q1/devel/scmgit-base-1.7.4.1.tgz
    pkg_add: Warning: package `scmgit-base-1.7.4.1' was built for a platform:
    pkg_add: DragonFly/i386 2.10.0 (pkg) vs. DragonFly/i386 2.13 (this host)
    pkg_add: Warning: package `p5-Error-0.17016nb1' was built for a platform:
    pkg_add: DragonFly/i386 2.10.0 (pkg) vs. DragonFly/i386 2.13 (this host)
    ...

    You’ll see many more warnings. The package wants to install TK and Python, but those packages are not available on this particula FTP server. But the -f flag means “Go ahead and install even if some dependencies are missing.” I use the -P to assign the package a new installation root directory in my jail’s root.

    Do I like these errors? No. But if I can install a working git, I can install pkgsrc and build a current package with all the dependencies. Log back into the jail and see if it works.

    # cd /usr
    # make pkgsrc-create
    If problems occur you may have to rm -rf pkgsrc and try again.

    mkdir -p /usr/pkgsrc
    cd /usr/pkgsrc && git init
    warning: templates not found /usr/pkg/share/git-core/templates
    Initialized empty Git repository in /usr/pkgsrc/.git/
    ...

    Wait a while, and you’ll have a working pkgsrc tree. From here, you can bootstrap pkgsrc:

    # cd /usr/pkgsrc/bootstrap
    # ./bootstrap
    # ./cleanup

    This gets you /usr/pkg/sbin/pkg_add.

    At this point, I consider my jail complete. While it doesn’t have all the third-party programs I need, I can now easily install them from within the jail, either from pkgsrc or with pkg_radd.

    Upgrading DragonFly BSD

    I have two DragonFly BSD boxes that I want to upgrade to the latest rev. At the moment, they’re running:

    $ uname -a
    DragonFly screw.lodden.com 2.10-RELEASE DragonFly v2.10.1.1.gf7ba0-RELEASE #1: Mon Apr 25 19:48:10 UTC 2011 root@pkgbox32.dragonflybsd.org:/usr/obj/usr/src/sys/GENERIC i386

    Unlike most other BSDs, DragonFly uses git for source code management. DragonFly provides make wrappers to git updates, however. If you don’t have the source code already installed, get it with:

    $ cd /usr
    $ make src-create

    mkdir -p /usr/src
    cd /usr/src && git init
    Initialized empty Git repository in /usr/src/.git/
    cd /usr/src && git remote add origin git://git.dragonflybsd.org/dragonfly.git

    Walk away for a little while, and you’ll come back to see:

    ...
    Checking out files: 100% (31175/31175), done.
    Already on 'master'
    cd /usr/src && git pull
    Already up-to-date.
    $

    This will get you the latest DragonFly BSD source code.

    Before going any further, look at /usr/src/UPDATING. This contains warnings and instructions for avoiding bumps in the upgrade process. For example, as I write this the post-2.10 UPDATING notes list several ISA-only device drivers that have been removed from the system. If I was running on an ISA system, I’d care about that. But I’m not, so I don’t. On to building the system!

    $ cd /usr/src
    $ make buildworld

    Once your world is built, follow up with:

    $ make kernel
    $ make installworld

    Those of us from other BSDs would expect an etcmerge or mergemaster here, but DragonFly replaces that with:

    $ make upgrade

    The make upgrade process is much faster and less interactive than any merge tool.

    After this is done, reboot. Log back in and you’ll find:

    $ uname -a
    DragonFly mwltest2.lodden.com 2.13-DEVELOPMENT DragonFly v2.13.0.49.gf6ce8-DEVELOPMENT #0: Tue Oct 18 10:51:40 EDT 2011 mwlucas@mwltest2.lodden.com:/usr/obj/usr/src/sys/GENERIC i386

    We’re running.

    My next task is to build a few jails and make them usable. But that’s for another post.

    Updates, October 2011

    I know people are waiting for the next books. So, how are they going?

    The last month or so has basically been a loss for writing. We bought a new house. I’ve painted most of the rooms, removed rancid carpet, stripped, sanded, stained, and sealed the underlying battered-but-intact hardwood floors, and generally made the house inhabitable.

    I now have a standing desk, made out of stuff found in abandoned Detroit buildings. Here’s the best photo I could take with my free-with-service BlackBerry.

    Standing Desk

    The base is wire shelving, with wheels. The top is from a desk. We drilled small holes in the bottom of the desktop so that it fit into the top of the shelf poles. The keyboard trays are left over from another project. If you’ve never had a desk on wheels, I highly recommend it. The standing desk has taken a few days to get used to, but is now pretty comfortable.

    The good news is, I can now resume writing.

    Before anyone asks: the wallpaper in the new office has to go. All wallpaper is a taint upon life, but this wallpaper is particularly egregious. I think I’ll let myself strip the walls as a reward for completing the OpenSSH and OpenBSD books. If I can ignore them that long.

    I also feel obliged to mention that if you look on the far right of the photo, atop the stereo speaker, you’ll see my ex-roommate’s skull.

    sudo environment purging and OpenSSH

    I recommend using sudo for privileged access to systems. I also recommend requiring keys for SSH authentication, with agent forwarding to trusted systems. The default settings in these two programs collide head-on when you become superuser via sudo and want to copy files from one server to another with scp or sftp.

    If you’re using an SSH agent, your environment contains the location of your authentication socket.

    # env | grep SSH
    SSH_CLIENT=192.0.2.2 51502 22
    SSH_CONNECTION=192.0.2.2 51502 198.0.2.10 22
    SSH_TTY=/dev/pts/1
    SSH_AUTH_SOCK=/tmp/ssh-aJpJNwwOTk/agent.35699
    #

    When you copy files with scp(1) or sftp(1), the client checks for a SSH authentication socket. If the client doesn’t find one, and the user account doesn’t have a private key on this system, and the remote server doesn’t support password auth, the client will not be able to log in.

    All as you would expect, right? But like any good firewall, sudo(8) removes all environment variables not explicitly permitted. To see what sudo(8) does to your environment, as well as all of sudo’s other settings, become root and run sudo -V.

    # sudo -V
    Sudo version 1.6.9p20

    Sudoers path: /usr/local/etc/sudoers
    Authentication methods: 'pam'
    Syslog facility if syslog is being used for logging: local2
    ...
    Environment variables to check for sanity:
    TERM
    LINGUAS
    LC_*
    LANGUAGE
    LANG
    COLORTERM
    Environment variables to remove:
    RUBYOPT
    RUBYLIB
    PYTHONINSPECT
    ...
    Environment variables to preserve:
    XAUTHORIZATION
    XAUTHORITY
    TZ
    PS2
    PS1
    PATH
    ...

    sudo sanity-checks some environment variables, deliberately strips others, and explicitly preserves a few.

    To use agent forwarding for SSH authentication while running as root, add the SSH environment variables to sudo’s configuration. While I could restrict this by groups, I’ll make this a default setting. Call up visudo and add a new default.

    Defaults env_keep += "SSH_CLIENT SSH_CONNECTION SSH_TTY SSH_AUTH_SOCK"

    Exit superuser, use sudo to become superuser again, and your environment will retain your SSH environment.

    While sudo can preserve any environment variables you wish, sudo strips the environment for very good reasons. Don’t retain environment variables unless you’re sure what they will do. And don’t retain easily-abused environment variables, such as LD_PRELOAD. If the superuser needs dangerous environment variables, put them in a separate configuration file and source that file after becoming superuser.

    DragonFly BSD Introduction

    As a long-time IT guy, I’ve grown accustomed to randomly discovering that the boss has purchased some new toy and wants me to put it into production. Usually, both the application and the underlying platform are completely incompatible with everything else we have. This demonstrates that one can grow accustomed to anything. This job is a little different, though. I came into the office to find that Fearless Leader installed a pair of new Dragonfly BSD machines and left me a shopping list of stuff I was to accomplish on them.

    As surprises go, it could be a lot worse.

    Why did Fearless Leader do this? As so much in this field, it started with annoyance — specifically, annoyance at ZFS requiring gigs and gigs of RAM for deduplication, even on OpenSolaris. HAMMER promised snapshots with more modest equipment requirements. This should help us sync different multiple servers.

    For the most part, Dragonfly is configured just like any other BSD. I thought it might be worth giving a quick run-through on how to start with Dragonfly, however. Besides, this is the most interesting thing I’ve done for a while. (Debugging multicast on Ubiquiti radios is both tedious and unproductive).

    First, let’s get these machines properly on the network. DHCP is fine for an install, but a server needs a static address. Dragonfly is based on the tail end of FreeBSD 4, also known as “what Lucas wrote his first tech book about,” so the configuration is fairly familiar. In /etc/rc.conf, add:

    hostname="red.example.com"
    ifconfig_em0="inet 192.0.2.151 netmask 255.255.255.128"
    defaultrouter="192.0.2.129"

    Reboot, and the network still works.

    One thing we noticed right away is that Dragonfly’s SSH server ships with passwords disabled. You must use public key auth or explicitly enable password auth. This presents a certain chicken-and-egg annoyance for us, because we distribute our public keys and our accounts via LDAP. When you install a Dragonfly machine, I suggest copying your authorized_keys file to the server before leaving the console.

    Now I need to install a whole bunch of software, such as text editors, a SNMP agent, and so on. Some of these programs will work as needed when installed from packages, but some will require special builds. I’ll start with the special builds. Dragonfly uses pkgsrc, NetBSD’s cross-platform ports project. I like a lot of things about pkgsrc, most obviously that it installs software in /usr/pkg. Install pkgsrc on your machine like so.

    # cd /usr/
    # make pkgsrc-create

    This downloads and installs the current pkgsrc tree. When complete, you can go to the package’s build directory and do the usual BSD-style bmake all install clean to install the package. (Note that you need bmake, not make.)

    If you’re happy with precompiled binary packages, just use pkg_radd.

    # pkg_radd net-snmp

    Wait a moment, and the package is installed from the remote FTP server.

    Both precompiled packages and packages you compile put their configuration information in /usr/pkg/etc.

    Other things I noticed:

  • Dragonfly has its own NTP daemon, dntpd. Enable it with dntpd_enable=YES in /etc/rc.conf. When you start dntpd, it forcibly syncs the clock if necessary.
  • Like FreeBSD, Dragonfly supports three firewall programs: ipfilter, IPFW, and PF. As of this time, PF is based on OpenBSD 4.4.
  • Dragonfly still mounts /proc by default.
  • Both Fearless Leader and I noticed that Dragonfly feels fast. This is a purely subjective statement, but both of the new machines feel very responsive. I look forward to seeing how much our typical load slows them down.

    UPDATE: Hello, Reddit’s Teeming Hordes! I’m not sure why this blurb on my intro to Dragonfly was worthy of sharing, but never let it be said that I’m a churlish host.

    Free Short Stories

    I now have three horror stories available on all ebook reader platforms and stores. For September 2011, you can get all of them for free via Smashwords. All have been previously published elsewhere. If you like one of them, please leave a review at your favorite ebook site. (Yes, this is a blatant, transparent attempt to gather reviews.) Follow the link, use the coupon code, and download the stories in your preferred format.

    Be warned: “Opening the Eye” contains blood and gore. The others are much more gentle.

    Wednesday’s Seagulls (coupon code BD29B)

    His plane crashed against a rocky Pacific island inhabited only by seagulls and a walking dead man.

    If he stops moving, the zombie will eat him.

    If he sleeps more than a couple hours, the zombie will eat him.

    But trying to trap the dead man might only make things worse.

    Breaking the Circle (coupon code AB94A)

    Twelve-year-old Chris learned that lycanthropy ran in her family. The hard way.

    On the desolate family farm dominated by her father’s alcoholism, everyone hides problems. As Chris grows, she spends one night a month locked in the basement and clawing at the door. She yearns for freedom, but can even transforming into a werewolf break the chains in Chris’ own mind?

    Opening the Eye (coupon code TQ34G)

    Street drug drought.

    Mindless need chews your bones. No way to feed it.

    An unthinkable solution to satisfy the hunger. Forever.

    If you can live that way…

    Replicating Routerboards

    I needed to mass-configure MikroTik Routerboards. Each needed a very similar but not identical configuration: they would have a unique management IP, and a unique username and password for their VPN connection back to my employer’s headquarters. I don’t have time or desire to do this routine configuration myself, so I needed a method that would let a less technical person do the work.

    You can back up and restore RouterOS configurations, but then I’d need to have the user do all sorts of pointy-clicky things to configure the device. Pointy-clicky is difficult to reliably reproduce. RouterOS also supports exporting the configuration as a script, but they warn that this script is not suitable for duplicating a system. I wound up using the export function, but modifying the resulting script to create a new configuration that could be loaded onto the new device.

    All of this was tested with Routerboard 750s, running RouterOS 5.6. I used one device as a master, and a second to test loading the new configuration. I also created a list of hostnames, IP addresses, and VPN usernames and passwords. (My team will manage these devices remotely, so the user will never see the username and password. If I deployed more than a couple dozen boxes, I’d need a different auth scheme.)

    First, make sure your Routerboard is in the default configuration. Reset to the factory defaults if necessary. Load the same version of RouterOS onto the new device as exists on your master device.

    Log onto your master device. Configure it the way you want, and export the configuration.

    admin@master> export file=cleanconfig
    admin@master> file print
    ...
    0 cleanconfig.rsc script 16 507 jan/02/1970 19:26:39
    ...

    This file is the seed of my configuration script. copy it to your desktop. (The detail-oriented among you will note that my configuration should include “Set the system clock.” Feel free to stay after class and clean the blackboards as a reward.)

    Unlike Cisco and its imitators, RouterOS isn’t modal. You don’t need to enter a configure mode to change the router. Instead, it has a hierarchical configuration method from the command line. For example, there’s an interface command, with an Ethernet subcommand. To work on the Ethernet interfaces, you would enter interface Ethernet. Starting a command with a leading slash tells RouterOS to go back to the root. Most of the actual commands within this level are fairly self-explanatory to anyone experienced with routers.

    /interface ethernet
    set 0 arp=enabled auto-negotiation=yes disabled=no full-duplex=yes l2mtu=1526 \
    mac-address=00:0C:42:5A:D9:50 mtu=1500 name=ether1-gateway speed=100Mbps

    The set command tells RouterOS to change a setting for an existing item. The Ethernet interface already exists, you just need to give it a configuration. Alternately, the add command tells RouterOS to add something new to a configuration, such as an IP address.

    /ip address
    add address=192.168.88.1/24 comment="default configuration" disabled=no \
    interface=ether2-master-local network=192.168.88.0

    The system has no IP address, so you must add one.

    If your script tries to add something that already exists, it will fail. For example, if your configuration already has a DHCP client setting, adding a new, identical one will terminate your script early. For my application, I had to remove the following from my master configuration.

    /interface ethernet switch
    /interface wireless security-profiles
    /ip hotspot profile
    /ip hotspot user profile
    /ip ipsec proposal
    /queue type
    /routing bgp instance
    /routing ospf instance
    /routing ospf area
    /system routerboard settings
    /user group
    /interface ethernet switch port
    /interface l2tp-server server
    /interface ovpn-server server
    /interface sstp-server server
    /interface wireless align
    /interface wireless sniffer
    /interface wireless snooper
    /ip accounting
    /ip accounting web-access
    /ip dhcp-client
    /ip dhcp-server config
    /ip firewall connection-tracking
    /ip firewall nat
    /ip firewall service
    /ip hotspot service-port
    /ip neighbor discovery
    /ip proxy
    /ip socks
    /ip ssh
    /ip traffic-flow
    /ip upnp
    /mpls
    /mpls interface
    /mpls ldp
    /port firmware
    /ppp aaa
    /queue interface
    /radius incoming
    /routing bfd interface
    /routing mme
    /routing ospf network
    /routing rip
    /store
    /system console
    /system health
    /system resource irq
    /system upgrade mirror
    /system watchdog
    /tool bandwidth-server
    /tool email
    /tool graphing
    /tool mac-server
    /tool mac-server ping
    /tool sms

    I then had some items that were different in my desired configuration and the default configuration. For example, the default 750 configuration has a firewall. I needed a firewall configuration that had no overlap with the default configuration. I added entries at the beginning of my script to remove that configuration. Similarly, I didn’t want the default IP address on this device.

    /ip pool remove 0
    /ip dhcp-server network remove 0
    /ip dns static remove 0
    /ip firewall filter remove 3
    /ip firewall filter remove 2
    /ip firewall filter remove 1
    /ip firewall filter remove 0
    /system logging remove 3
    /system logging remove 2
    /system logging remove 1
    /system logging remove 0
    /ip address remove 0

    Other parts were more tricky. I wanted to configure the Ethernet interfaces, but I didn’t want to change the MAC address of the interfaces. I removed the mac-addr statements from the Ethernet interface configuration.

    Now that I have a clean master script, I copy it to a separate file for the slave configuration script. In the copy, I change the IP address, hostname, username, and passwords as necessary.

    I could then copy the script to my target machine and run

    admin@target> import configscript.rsc
    admin@target>

    If the script doesn’t exit silently, it failed. Compare the target platform’s new configuration to your script to see where it failed, or run your script piecemeal to see where it crashes.

    This was a good day’s work to learn how to do, but now I can hand my script to a junior tech and tell him to set up a couple dozen of these without bothering me. It’ll save me days of my time in the long run.

    Book updates, August 2011

    I completed a first draft of the OpenSSH book last night around 10:30PM EDT. It’s out for tech edit now. At this point, I’m going systematically through the tech edits and making sure I’ve corrected the earlier chapters. After that, the manuscript goes to copyediting. Once copyedit is complete, I’ll release the ebook and start contracting out the POD version.

    I normally write both nonfiction and fiction simultaneously. When I get frustrated with one project, I switch to the other. The context switch clears my brain. When I return to the vexing project, I can approach the problem fresh and work through it quickly.

    I decided to do two nonfiction projects simultaneously this summer. In retrospect, this was a mistake. When I got frustrated with one project, I switched to the other… and found myself still frustrated. Perhaps I can do two nonfiction projects simultaneously, but OpenSSH and OpenBSD have a lot in common. One is just a subset of the other. My frustrations would probably be reduced if I knew what I was doing, but if I knew what I was doing, I wouldn’t write the book.

    Lesson learned. If I want to write two nonfiction books simultaneously, they must be wildly diverse.

    The OpenBSD book has therefore moved slowly. It’s further complicated by moving over the next couple weeks. I’ll be full-out cranking on the OpenBSD book this fall, however.

    I predicted that the OpenSSH book would be 30,000 words. The first draft came in at 29,977 words. I am amazed; usually my books come in at 25-50% over the predicted word count. Perhaps I’m learning. But I’m probably just lucky.

    Colin Harvey, RIP

    This is off my usual track, but it’s my blog, so I’m free to do so.

    Science fiction writer Colin Harvey died Monday, 15 August 2011, of an unexpected stroke, at age 50. He’d published several hard SF novels and edited a variety of anthologies.

    I was lucky enough to have Colin in my writing critique group.

    One of the ways to improve your writing is to exchange manuscripts with other people. By critiquing others’ work, and getting critiques on your own, you see what works and what doesn’t. (Strictly speaking, I should mention that the purpose of the critiques is not to improve the manuscript you just submitted, but to improve what you write in the future. You can’t do a huge amount to fix what you’ve already written.)

    A good critique group is a weird thing. You want your critiquers to like what you’ve written. But you want them to assault your work with everything they have, point out every deficiency, and push you to make you better. The closest comparison I can make is to a martial arts school, where you help your partners improve even as you smack the crap out of them, being friendly and kind and forceful simultaneously. In a successful writing group, you develop unique friendships, even with people you don’t know. Colin was one of those friends.

    I joined my current writing group at the beginning of 2007. Colin was a member. In the years since, Colin simultaneously beat the living crap out of my work and supported me as a writer. My work dramatically improved, thanks in large part to his efforts. (Not to discount the other crit group members; they’ve all been invaluable. Even Rob.)

    I still have a copy of every message that has passed through the writing group since I joined. Rather than just say what a tragedy his loss is, I thought it might be more meaningful to extract some of what he said about writing and offer it here. It’s gauche to repost semi-private conversations, of course. I don’t believe Colin would mind these particular clips. He had a fantastic sense of humor, openly documented his life on his blog, and said all of this about my work on a mailing list archived on Yahoo Groups.

  • (On his well-deserved critiques of the first piece I submitted): “I’m aware that this mail reads like a thorough kicking, and I’m trying hard to find some positives”
  • (On receiving harsh critiques): “I’ll take this opportunity to thank you and all the others for the comments…”
  • (On theme in writing): I believe that the themes that a writer writes about come from within. So duplicity and betrayal feature largely in mine for reasons that even I don’t understand. I believe that if someone has enough belief and talent to write, then themes can’t be imposed. Topics or subjects can, but not theme.
  • (On any crit group member making a sale): “Congratulations”
  • (On synopsis): I think the whole point of the synopsis discussion is that various elements of the group have recognized that their work –as it stands– is already of publishable quality, but that there are barriers to that work being published. One of the barriers is the synopsis, or –as in most submissions generally– an inadequate synopsis. Yes, an agent / publisher will look at your first one, two or three chapters and it may be that the extract is sufficiently good to off-set an inferior synopsis. But many agents just don’t have or want to spend the time if they don’t have to. It’s all about shortening the odds.
  • (On scenes): Scenes should -for me- serve one of three purposes
    i ) To move the plot along
    ii) To set a scene or to
    iii) Illuminate the character(s) in it
  • (On advice from non-writers): Why are you letting a failed writer tell you what to write? And before you argue that — how many best-sellers has xxxxxx WRITTEN? How many awards has he won for his fiction? (Those are all rhetorical questions, btw. I know about lack of confidence – honest!)
  • (On research): I read as I go along. I never know what I need to read up on.
  • (On critiques): The whole point of crits is not to tell you what you do know, but to smack you in the side of the head from a completely unexpected direction.
  • (Whenever anybody said anything nice about his successes): Thank you,
  • (On vocabulary): recuperance? What sort of word is that? Did you just make it up?
  • (On getting published): Persistence, persistence, persistence.
  • (On future technology): I don’t see shaving disappearing anytime soon….
  • (On the Star Trek reboot): You went expecting a storyline? You’re clearly an optimist.
  • (on someone on the critique group mailing list whinging about the publishing industry): Rant on as much as you need to, as far as I’m concerned.
  • (after two years of discussing outlining novels versus winging the story, during which he converted me from a hard-core winger to a hard-core outliner, and I espouse the benefits of outlines to other group members): Oh, I am *so* enjoying the poacher-turned-gamekeeper aspect of this e-mail….
  • (On writing rules): The only ‘rule’ is if it works, do it.
  • (On the last piece of mine he critiqued): This is a very, very good story.
  • I could go on, and on, and on, but I’ve spent hours on this. And Colin would tell me to get back to writing my own work.

    Colin never stopped improving his work. And he never stopped improving mine, either. He recently wrote novels that he hadn’t had time to finish marketing. I want to see both Black Death and Ultramassive in print. The latter is cool SF, and the former scared the crap out of me.

    Colin left the writing group earlier this year, but he and I agreed to continue exchanging manuscripts in a less public forum. By sheer chance, we agreed to take a hiatus in August — we both had big family projects underway. If he’d spent the last month of his life reviewing my sewage work instead of spending time with his family, I’d feel pretty bad. If I’d still owed him a crit when he passed, I’d feel ghastly.

    Colin spent his last days with people more important to him than myself. And that’s how it should have been.

    ISC dhcpd and MAC prefixes

    We have a network at the office without a firewall. Several of our technical folks run a whole mess of oddball network protocols, and maintaining a network firewall would take more manpower than it’s worth. We hand these techs a network cable and tell them to not let their gear get broken into, and then heckle them mercilessly when their server becomes a haven for Eastern European porn.

    But the boss just got back from ClueCon, where he learned about a whole bunch of nasty exploits for older Cisco phones. These techs have VoIP phones on their desks. The decision was made to give the VoIP phones private addresses, overlaid on the public addresses. Yes, a firewall would be much better, but the maintenance overhead was too high in this situation.

    Our VoIP phones have have MAC addresses in particular ranges. For example, a certain model of phone has MAC addresses beginning with 00:03:e3, others begin with 00:03:6b, and the third begin with 00:07:eb. If a device’s MAC address begins with one of these, it should get an address in 172.19.22.0/24. Otherwise, it should get an address in the public pool of 192.0.2.0/24. I’m using ISC dhcpd 3.1.

    Start by breaking the devices into classes. Each class will have its own IP configuration. dhcpd has a pattern-matching feature that lets you match MAC addresses like this dhcpd.conf snippet.

    class "sip-phone" {
    match if (binary-to-ascii (16,8,":",substring(hardware, 0, 4)) = "1:0:3:e3")
    or (binary-to-ascii (16,8,":",substring(hardware, 0, 4)) = "1:0:3:6b")
    or (binary-to-ascii (16,8,":",substring(hardware, 0, 4)) = "1:0:7:eb");
    log (info, (binary-to-ascii (16,8,":",substring(hardware, 0, 4))));
    }

    class "other" {
    match if not (binary-to-ascii (16,8,":",substring(hardware, 0, 4)) = "1:0:3:e3")
    and not (binary-to-ascii (16,8,":",substring(hardware, 0, 4)) = "1:0:3:6b")
    and not (binary-to-ascii (16,8,":",substring(hardware, 0, 4)) = "1:0:7:eb");
    log (info, (binary-to-ascii (16,8,":",substring(hardware, 0, 4))));
    }

    So, if a host has a hardware (MAC) address matching one of the patterns in the “sip-phone” class, it’ll be classified as a sip phone. If the hardware address doesn’t match anything in the sip-phone class, it’ll be classified as other.

    What does that horrible string mean? Read dhcp-eval for details, but here’s the short and nasty version. Inside dhcpd, hardware addresses are binary. The binary-to-ascii function transforms a binary field into characters. The 16,8,: tells binary-to-ascii how to parse the binary data and where to separate the characters. The substring function takes a small portion of the ASCII string, specifically places 0 through 4. The characters are a hardware label (a 1 means Ethernet) followed by a colon, followed by the MAC address. So, to match all Ethernet MAC addresses starting with 00:03:e3, we must search for 01:00:03:e3, shown without the leading zeroes as 1:0:3:e3.

    The Boolean logic around these comparisons groups the MAC addresses into classes by MAC.

    The interesting bit here is the “log” statement. I wanted to see how the binary-to-string and the substring functions mauled the original MAC address. This LAN has a sufficiently small DHCP request rate that I was able to log the first sixteen bytes of each hardware identifier (including the leading 01: for Ethernet). This helped me figure out what the heck I was finding as I struggled through dhcp-eval(5). These logs were written to the DHCP server log. Remove them when you’re done testing.

    In this dhcpd.conf snippet we assign the two classes separate address ranges.

    shared-network techs {
    subnet 172.19.22.0 netmask 255.255.255.0 {
    option routers 172.19.22.1;
    pool {
    allow members of "sip-phone";
    range 172.19.22.10 172.19.22.100;
    next-server 192.0.2.130;
    }
    }

    subnet 192.0.2.0 netmask 255.255.255.0 {
    option routers 192.0.2.1;
    pool {
    allow members of "other";
    range 192.0.2.100 192.0.2.200;
    }
    }
    }

    The DHCP server has an interface on this subnet. I added a private IP to the interface, so that it has both 192.0.2.0/24 and a 172.19.22.0/24 addresses. The shared-network statement tells the DHCP server to not be shocked when it sees both subnets on the local interface.

    So far, this works.

    UPDATE: To all the folks who say that this network could certainly be VLAN’d and firewalled. There is no technical reason this couldn’t happen. The problems are political. Political problems are just as real as technical problems.