Category Archives: Linux

Patching DSDT in recent Linux kernels without recompiling

Up until a year or two ago, the Linux kernel let you replace the ACPI DSDT by adding a customized version to the initrd. However, more recent versions disable that by default. If you’re using Grub2 as your bootloader though, the alternative is simple: just add acpi /boot/dsdt.aml to your Grub config. If you prefer a more elegant solution, just add the attached 01_acpi.txt to /etc/grub.d, renamed it to 01_acpi and chmod +x it; then run update-grub2 to rebuild your Grub config. It originally came from ubuntuforums.org and I removed the -e flag in the acpi line, which caused the new DSDT only to be visible to Grub, but not to the OS.

I believe the acpi command in Grub2 originally came from the Hackintosh community – messing around with DSDTs is a lot more common there because Mac OS X is rather picky.

For those of you who don’t know what I’m talking about: the DSDT describes certain hardware features your PC has, such as buttons, CPU power save modes, and lots of other things. Some mainboards have very poorly done BIOSes that have equally messed up DSDT tables. I won’t go into a lot of detail regarding how to fix those here though (it’s as simple as cat /proc/acpi/dsdt > dsdt.dat; iasl -d dsdt.dat; editing dsdt.dsl to your liking; iasl -tc dsdt.dsl (this last step will probably produce a number of errors that can be solved by googling for the error number and making the appropriate changes in dsdt.dsl)).

Another valuable hint for people messing around with DSDTs: the DSDT is not the only place that can contain this kind of information, the other place would be the SSDT and possibly additional SSDTs. You can find them in /sys/firmware/acpi/tables and decompile them just like the DSDT as described above. Instead of recompiling the SSDT by itself, you could probably also consider merging it into your custom DSDT at the appropriate places.

Using motd to warn you of upcoming fsck

Has it ever happened to you that you remotely rebooted a Linux server and wondered why after 10 minutes you still couldn’t Ping or SSH into it? Quite often, this is caused by a lengthy run of fsck on one of your ext3 file systems (on current multi-TB disks, they can take an hour or longer). They usually get triggered automatically around every six months or 30 mounts, and there’s no easy way of knowing when it will happen.

My solution to this is rather simple: tune2fs -l tells you when the next fsck is coming up, so why not run a script upon every boot and have it write that information into /etc/motd so that you can see it every time you SSH in? The script is below (as a bonus, drives being fscked in less than 5 mounts or a week are printed in red), so all you need to do is put it somewhere on your hard drive and add something like
# Add FSCK status to MOTD
[ -f /root/fsck_stats.sh ] && bash /root/fsck_stats.sh >> /var/run/motd

to the init script that generates your motd (on Debian Squeeze, that would be /etc/init.d/bootlogs).

#!/bin/bash

echo

for disk in /dev/sda1 /dev/mapper/vm--storage-vms /dev/sdb1
do
cur_mounts=$(tune2fs -l $disk | grep "Mount count:" | awk '{print $3}')
max_mounts=$(tune2fs -l $disk | grep "Maximum mount count:" | awk '{print $4}')
diff_mounts=$(echo $max_mounts-$cur_mounts | bc)
last_check=$(tune2fs -l $disk | grep "Last checked:" | awk '{print $3}')
next_check=$(tune2fs -l $disk | grep "Next check after:" | awk '{print $4" "$5" "$6" "$7" "$8}')
next_check_timestamp=$(date -d "$next_check" "+%s")
cur_timestamp=$(date "+%s")
diff_next=$(echo $next_check_timestamp-$cur_timestamp | bc)

color=""
[ $diff_mounts -lt 5 ] && color="\033[31m"
[ $diff_next -lt 604800 ] && color="\033[31m"

echo -e "$color Next FSCK on $(basename $disk): $next_check or in $diff_mounts mounts\033[0m"

IPv6 router on Linux

Setting up Linux as an IPv6 router is really easy. Even if your ISP doesn’t do IPv6 yet (like mine), there’s no reason not to get an IPv6 tunnel from Tunnelbroker.net and be IPv6-ready within minutes.

  1. Do a basic install of your favorite Linux distribution.
    Since my server runs Xen, I just did xen-create-image –ip=192.168.200.5 –netmask=255.255.255.0 –gateway=192.168.200.1 –nameserver=192.168.200.23 –mirror=http://ftp.de.debian.org/debian/ –passwd –hostname=router-ipv6 –dist=squeeze –arch=i386 –size=4G –swap=1G –dir=/data/vms/router-ipv6 –memory=64M –role=udev –pygrub and ran ln -s /etc/xen/router-ipv6.cfg /etc/xen/auto to have it automatically start upon reboot. To start the VM, do xm create router-ipv6.cfg
  2. SSH into the virtual machine and configure the LAN and the WAN interface. Since I’m using a tunnel, my WAN interface is a 6in4 interface; if you’re using a physical one you’ll need to manually edit the Xen VM config file to add the physical interface to the VM. So we’re adding the following lines to /etc/network/interfaces
    iface eth0 inet6 static
    address 2001:470:xxxb:xxxx::1
    netmask 64

    auto 6in4
    iface 6in4 inet6 v4tunnel
    address 2001:470:xxxa:xxxx::2
    netmask 64
    endpoint 216.66.80.30
    gateway 2001:470:xxxa:xxxx::1
    up ip route add ::/0 dev 6in4

  3. Next, edit /etc/sysctl.conf and set net.ipv6.conf.all.forwarding=1 by removing the comment sign from the beginning of the line.
  4. apt-get install radvd and then edit /etc/radvd.conf to look like this:
    interface eth0
    {
    AdvSendAdvert on;
    AdvLinkMTU 1280;
    prefix 2001:470:xxxb:xxxx::1/64
    {
    AdvOnLink on;
    AdvAutonomous on;
    };
    RDNSS 2001:470:xxxb:xxx:yyyy:yyyy:yyyy:yyyy
    {
    };

    Most of this is pretty self-explanatory (the prefix line should contain the address of the router’s network interface and everything else just enables router advertisements), however the RDNSS line needs to point to the IPv6 address (it will automatically get one after you finish step 6) of your local DNS forwarder.
  5. Next, you’ll probably want to configure the firewall so that your computers can’t be accessed from outside (remember, with IPv6 every device gets a publicly routable address). apt-get install shorewall6 and then edit the following files to configure it:
    In /etc/default/shorewall6: startup=1 (enables the firewall) and wait_interface="6in4" (your WAN interface)
    In /etc/shorewall6/zones: Add the lines fw firewall, net ipv6 and loc ipv6
    In /etc/shorewall6/interfaces: Add the lines net 6in4 detect and loc eth0 detect
    In /etc/shorewall6/policy: Add the lines net all REJECT notice, loc all ACCEPT, fw all ACCEPT and all all REJECT notice
    In /etc/shorewall6/rules: Configure the firewall rules to your liking. I added Ping(ACCEPT) all all to allow incoming pings (I don’t believe in this security-by-obscurity stuff). I also added ACCEPT all loc:2001:470:xxxb:xxxx:zzzz:zzzz:zzzz:zzzz because that machine has its own IPv6-configured firewall.
  6. Reboot the VM.

All your IPv6-ready clients should start picking up addresses automatically. Linux, Mac OS X and iPhones do as expected and base their IP on the MAC address. Windows 7 does the same, but also makes up a random IP which gets used by default for all outgoing connections due to privacy reasons. On Windows XP, you need to manually add IPv6 to the network protocols in the network connection properties, after which it’ll behave similarly to Windows 7.
The DNS server announced by radvd however only gets picked up by the iPhone. Mac OS X only supports manually-configured IPv6 DNS servers as far as I can tell. Windows automatically configures fec0:0:0:ffff::1, fec0:0:0:ffff::2 and fec0:0:0:ffff::3 as its DNS servers; you could add one of these addresses to your DNS server (and add some other address in the fec0:0:0:ffff::/64 range to your IPv6 router VM’s LAN interface so that clients can actually find a route to it), but unfortunately the site-local prefix fec0::/10 has been deprecated for more than half a decade and should no longer be used. But fear not, it’s perfectly fine to talk to your DNS server using IPv4 – it will still resolve AAAA (IPv6 A) queries without issues. And I expect IPv4 to stay around for at least another decade, so you’re not likely to run into trouble for a long time.

    ISC DHCPd: Dynamic DNS updates against secure Microsoft DNS

    UPDATE 2016: I have posted a much simpler way that works with DNS delegations so that you can have your domain controllers maintain the records necessary for their discovery in Microsoft DNS, while all your clients are in a BIND DNS server which can be easily interfaced with ISC DHCPd.

    ISC DHCPd is capable of Dynamic DNS updates against servers like BIND that support shared-key authentication or any other server that supports unauthenticated updates (such as BIND or Microsoft DNS with secure updates disabled).

    So, what to do if you want to run ISC DHCPd on your Windows network, which is obviously running Microsoft’s DNS server? BIND’s nsupdate tool supports Microsoft’s Kerberos authentication scheme when using the -g flag (the -o flag is only necessary for Windows 2000 Server, but not anymore for Windows Server 2008 R2), and DHCPd supports on commit/release/expiry blocks that let you run scripts upon these events. So here is my script:

    #!/bin/bash

    ## CONFIGURATION ##

    realm=EXAMPLE.COM
    principal=dhcpduser@$realm
    keytab=/root/dhcpduser.keytab
    domain=example.com
    ns=example-domain01.example.com

    export KRB5CCNAME="/tmp/dhcp-dyndns.cc"

    keytab can be generated using

    $ ktutil

    ktutil: addent -password -p dhcpduser@EXAMPLE.COM -k 1 -e aes256-cts-hmac-sha1-96

    Password for dhcpduser@EXAMPLE.COM:

    ktutil: wkt dhcpduser.keytab

    ktutil: quit

    VARIABLES

    action=$1
    ip=$2
    name=$(echo $3 | awk -F '.' '{print $1}')
    mac=$4

    usage()
    {
    echo "USAGE:"
    echo $0 add 192.0.2.123 testhost 00:11:22:33:44:55
    echo $0 add 192.168.0.127 "" 00:11:22:44:33:55
    echo $0 delete 192.0.2.123 testhost 00:11:22:33:44:55
    echo $0 delete 192.0.2.127 "" 00:11:22:44:33:55
    }

    if [ "$ip" = "" ]; then
    echo "IP missing"
    usage
    exit 101
    fi
    if [ "$name" = "" ]; then
    #echo "name missing"
    #usage
    #exit 102
    name=$(echo $ip | awk -F '.' '{print "dhcp-"$1"-"$2"-"$3"-"$4}')

    if [ "$action" = "delete" ]; then
    name=$(host $ip | awk '{print $5}' | awk -F '.' '{print $1}')

    echo $name | grep NXDOMAIN 2>$1 >/dev/null
    if [ "$?" = "0" ]; then
    exit 0;
    fi
    fi
    fi

    ptr=$(echo $ip | awk -F '.' '{print $4"."$3"."$2"."$1".in-addr.arpa"}')

    KERBEROS

    #export LD_LIBRARY_PATH=/usr/local/krb5-1.7/lib
    #export PATH=/usr/local/krb5-1.7/bin:$PATH

    klist 2>&1 | grep $realm | grep '/' > /dev/null
    if [ "$?" = 1 ]; then
    expiration=0
    else
    expiration=$(klist | grep $realm | grep '/' | awk -F ' ' '{system ("date -d \""$2"\" +%s")}' | sort | head -n 1)
    fi

    now=$(date +%s)
    if [ "$now" -ge "$expiration" ]; then
    echo "Getting new ticket, old one expired $expiration, now is $now"
    kinit -F -k -t $keytab $principal
    fi

    NSUPDATE

    case "$action" in
    add)
    echo "Setting $name.$domain to $ip on $ns"

    oldname=$(host $ip $ns | grep "domain name pointer" | awk '{print $5}' | awk -F '.' '{print $1}')
    if [ "$oldname" = "" ]; then
    oldname=$name
    elif [ "$oldname" = "$name" ]; then
    oldname=$name
    else
    echo "Also deleting $oldname A record"
    fi

    nsupdate -g <
    server $ns
    realm $realm
    update delete $oldname.$domain 3600 A
    update delete $name.$domain 3600 A
    update add $name.$domain 3600 A $ip
    send
    UPDATE
    result1=$?
    nsupdate -g <
    server $ns
    realm $realm
    update delete $ptr 3600 PTR
    update add $ptr 3600 PTR $name.$domain
    send
    UPDATE
    result2=$?
    ;;

    delete)
    echo "Deleting $name.$domain to $ip on $ns"
    nsupdate -g <
    server $ns
    realm $realm
    update delete $name.$domain 3600 A
    send
    UPDATE
    result1=$?
    nsupdate -g <
    server $ns
    realm $realm
    update delete $ptr 3600 PTR
    send
    UPDATE
    result2=$?
    ;;
    *)
    echo "Invalid action specified"
    exit 103
    ;;
    esac

    result=$result1$result2
    if [ "$result" != "00" ]; then
    echo "DHCP-DNS Update failed: $result"
    logger "DHCP-DNS Update failed: $result"
    fi

    exit $result

    and here is the relevant part of my dhcpd.conf:

    on commit {
    set noname = concat("dhcp-", binary-to-ascii(10, 8, "-", leased-address));
    set ClientIP = binary-to-ascii(10, 8, ".", leased-address);
    set ClientMac = binary-to-ascii(16, 8, ":", substring(hardware, 1, 6));
    set ClientName = pick-first-value(option host-name, host-decl-name, config-option host-name, noname);
    log(concat("Commit: IP: ", ClientIP, " Mac: ", ClientMac, " Name: ", ClientName));

    execute("/root/dhcp-dyndns.sh", "add", ClientIP, ClientName, ClientMac);
    }
    on release {
    set ClientIP = binary-to-ascii(10, 8, ".", leased-address);
    set ClientMac = binary-to-ascii(16, 8, ":", substring(hardware, 1, 6));
    log(concat("Release: IP: ", ClientIP, " Mac: ", ClientMac));

    cannot get a ClientName here, for some reason that always fails

    execute("/root/dhcp-dyndns.sh", "delete", ClientIP, "", ClientMac);
    }
    on expiry {
    set ClientIP = binary-to-ascii(10, 8, ".", leased-address);

    cannot get a ClientMac here, apparently this only works when actually receiving a packet

    log(concat("Expired: IP: ", ClientIP));

    cannot get a ClientName here, for some reason that always fails

    execute("/root/dhcp-dyndns.sh", "delete", ClientIP, "", "0");
    }

    Figuring this all out took me several afternoons because Kerberos 5 1.8 has a bug where forwardable tickets (which is the default on Debian) are incompatible with nsupdate. Manually compiling 1.7 or getting 1.9 from the experimental Debian branch helps, as does adding the -F flag to kinit (which I did in the script above) to make the ticket non-forwardable.
    I filed a bug with Debian (#611906) and Sam Hartman (thanks!) helped me track it down.

    EDIT 2011-11-17:
    I recently ran into the issue that if the AD server could not be reached, dhcpd would stall (and not respond to DHCP requests during that time) until nsupdate reached its timeout. The fix is simple: rename dhcp-dyndns.sh to dhcp-dyndns-real.sh and create dhcp-dyndns.sh with the following contents to fork off the real script into the background:
    #!/bin/bash

    $(dirname $0)/dhcp-dyndns.sh $@ 2>&1 | logger &

    Also, I updated the on commit section in the dhcpd.conf excerpt above to compose a fallback name from the IP address if the client provides no hostname. This fixes the issue that nsupdate tries to register a record based on the name and fails.

    Building your own OpenDirectory server on Linux

    OpenDirectory is a feature included with Mac OS X Server. Wouldn’t it be nice if you could use it without having to spend hundreds of dollars on a server license? Wouldn’t it be great if you could add it into your existing Linux-based OpenLDAP server? It’s actually quite easy because OpenDirectory is a standard OpenLDAP server with a special Apple schema.

    0. Prerequisites
    – OpenLDAP server with Samba integration (I’m runnig it on a Ubuntu 8.04 server, using the standard OpenLDAP and Samba packages). I won’t go into the details of how to set this up, there are lots of tutorials around the web on this.
    – some kind of LDAP admin tool, I used phpLDAPAdmin
    – Mac OS X 10.5 Leopard clients

    1. Adding the Apple schema to your OpenDirectory server
    It is located in /etc/openldap/schema/apple.schema on any Mac. Copy this file to your OpenLDAP server and add it to your slapd.conf.
    You may run into the problem that apple.schema references some samba.schema entries that were deprecated with Samba 3. Specifically, these are acctFlags, pwdLastSet, logonTime, logoffTime, kickoffTime, homeDrive, scriptPath, profilePath, userWorkstations, smbHome, rid and primaryGroupID, so you’ll need to editapple.schema and replace these with their Samba 3 counterparts.
    Now, restart the OpenLDAP daemon so it recognizes the changes.

    2. Adding some Mac-specific attributes to your LDAP server
    Add an ou=macosx branch to your LDAP tree, under which you’ll need to create ou=accesscontrols, ou=augments, ou=automountMap, ou=autoserversetup, ou=certificateauthorities, ou=computer_groups, ou=computer_lists, ou=computers, ou=filemakerservers, ou=locations, ou=machines, ou=maps, ou=mount, ou=neighborhoods, ou=places, ou=preset_computer_groups, ou=preset_computer_lists, ou=preset_computers, ou=preset_groups, ou=preset_users, ou=printers, and ou=resources.
    To all your LDAP groups, add the apple-group objectClass. To all your LDAP users, add the apple-user objectClass.

    3. Connecting your Mac to the LDAP directory
    On your Mac, go into Directory Access and add your LDAP server. Choose OpenDirectory as the server type and adjust the Samba mappings to match your changes from step 1. Here is a plist you can import into Directory Access that already has these mappings corrected: LDAPv3_Unix_Samba3_OD.plist.
    If you want your other clients to automatically use this mapping, create a cn=config branch in your LDAP tree and use the Write to Server button in Directory Access.

    4. Use Workgroup Manager to set network home folders, managed preferences, …
    Now, you can use Workgroup Manager to manage network home folders and managed preferences, just like you would on a Mac server.  You’ll need to authenticate using an LDAP user who has full write privileges to the directory (as set in slapd.conf). The standard cn=admin,dc=example,dc=com user will NOT work.

    5. Conclusion
    Almost everything works, except for:
    – adding new users and group through Workgroup Manager
    – solution: unknown
    – assigning directory admin privileges to users through Workgroup Manager
    – solution: using an OpenLDAP server set up to use cn=config instead of slapd.conf. This will also require going into Directory Access again and adding the OLCBDBConfig, OLCFrontEndConfig, OCGlobalConfig, OLCSchemaConfig and OLCOverlayDynamicID record types back in (they are included in the OpenDirectory mapping, but I deleted them from mine because they only cause error messages on an OpenLDAP server with slapd.conf configuration).

    Here are all the web sites that helped me in the process of figuring this out:
    http://docs.info.apple.com/article.html?path=ServerAdmin/10.4/en/c6od15.html (this one is especially important because it explains what to do if your LDAP server is not set up for SASL authentication)
    http://www.emmes-world.de/mac-afp-homes.html (this one describes a similar setup and was my most important resource)
    http://rajeev.name/blog/2006/09/09/integrating-mac-os-x-into-unix-ldap-environment-with-nfs-home-directories/
    http://www.netmojo.ca/blog/tag/ldap/
    http://www.macdevcenter.com/pub/a/mac/2003/08/26/active_directory.html?page=2

    7. Further Information
    Since you’re not using Kerberos for authentication, you may want to look at securing your LDAP connections with SSL. Here are some links that talk about it:
    http://www.novell.com/coolsolutions/feature/19965.html
    http://www.afp548.com/article.php?story=20071203011158936

    Someone else also wrote a blog post about Setting up a Linux server for OS X clients, in which they also describe how to incorporate Kerberos into the whole equation. That’s certainly something worth considering.