= Modifying Orchard NetBoot Offerings = Orchard's !NetBoot service runs on a Debian GNU/Linux virtual server and replicates much, but not all, of the functionality of a Mac OS X Server based !NetBoot service. This document provides some background information on !NetBoot, and general instructions on adding a new !NetBoot set to the server. Instructions on how to set-up your own !NetBoot server in your department will be published soon. == Terminology == * '''!NetBoot''': A service that allows a Macintosh to boot up over a TCP/IP network from a !NetBoot set hosted on a server, so that the local system disk, if present, is not used as the boot volume. Broadly, this can be thought of as booting off an external disk that is located across a network. * '''!NetBoot set''': A folder that resides on a !NetBoot server containing boot components, identifying information, and a disk image of a bootable system volume. Multiple !NetBoot sets may be offered by a !NetBoot service, with a default set always designated. * '''Diskless !NetBoot''': Because the bootable system volume of a !NetBoot set is made available to the client with read-only access, any filesystem changes that take place during the !NetBoot session, including writes to swap, must be written to some other storage. By default, a local volume on the client is used for this writable storage, but this dependency makes manipulations of that volume such as reformatting and imaging impossible. To avoid this limitation, a !NetBoot set can be configured such that it will use alternative storage locations for this task - this is known as Diskless !NetBoot. * '''BSDP''': Boot Service Discovery Protocol is the name of Apple's !NetBoot discovery and configuration protocol, which is designed to be encapsulated within DHCP packets using certain vendor specific options (see options 43 and 60 in [[http://tools.ietf.org/html/rfc2132|RFC 2132]], and [[http://www.opensource.apple.com/source/bootp/bootp-170/Documentation/BSDP.doc|Apple's BSDP documentation]]). While sometimes referred to as 'BSDP packets', these are in fact normal DHCP packets that simply encapsulate BSDP information in a standard way. == Background Information == The most basic Macintosh !NetBoot service has three essential components: * A specially configured DHCP service, to respond to BSDP requests (encapsulated in DHCP packets). * A TFTP service, to allow downloading of initial boot files from a !NetBoot set by the client. * An HTTP or NFS service, to provide read-only access to the disk image containing the system volume for the !NetBoot set. These services do not have to be running on the same server, although it is typical that they are. When a Mac is instructed to !NetBoot (for example, when started with the 'N' key held down), it will first attempt to obtain an IP address via the normal DHCP process. After obtaining an IP, the Mac will send out a BSDP request encapsulated in a different DHCP packet that is recognised by the specially configured DHCP service on the !NetBoot server, which may or may not be the same DHCP service that provided the IP address moments before. The initial information from the client informs the !NetBoot server of the client's hardware type, and the server responds by offering a list of platform-appropriate !NetBoot sets. The client responds with a selection, and finally the server responds with all the necessary information for booting from the selected set. At a minimum, four packets of BSDP information are exchanged before the client can begin its initial !NetBoot. The server must always offer one default !NetBoot set, which is automatically selected by the client when booting with the 'N' key depressed. Additional !NetBoot sets can be viewed and selected by booting with the 'Option' key depressed on newer Macintoshes. The option to view and select !NetBoot sets via the 'Startup Disk' preference pane in System Preferences is '''not''' currently available when using a non-Apple-provided !NetBoot service, due to nonstandard ports on which the Preference Pane expects to receive the server's BSDP responses. In order to offer Diskless !NetBoot functionality, a !NetBoot server typically has to offer a fourth service: a read-write network share to which filesystem changes and swap data can be written, with a special directory structure created to accommodate each client's !NetBoot sessions. However, to sidestep the complexity involved in re-implementing this, we modify a startup script on our !NetBoot images so that clients instead create and mount a RAM-disk at boot. This provides a small but adequate amount of local, off-disk, read-write storage for changes to be written to, obviating the need for a network based solution. Since all DHCP communications are initiated by a client broadcasting on the local network, they cannot directly reach a !NetBoot server located on another subnet. This limitation is overcome via [[http://en.wikipedia.org/wiki/Dynamic_Host_Configuration_Protocol#DHCP_relaying|DHCP Relaying]], which when working with Cisco routers is achieved by configuring one or more IP Helper Addresses on each subnet. These IP Helper Addresses can point to various DHCP servers external to the subnet, including !NetBoot servers. Therefore, it is usually necessary to request the addition of an IP Helper Address in order to make the !NetBoot service available on a new subnet. ---- == Adding a NetBoot Set to the Server == === Step 1: Transferring a new NetBoot Set === !NetBoot sets are stored in the directory `/srv/netboot//sets/` on the !NetBoot server. This directory is writable by users in the group ''NSMS\nsms_group'', to which all NSMS staff AD accounts should belong. Before transferring a new !NetBoot set to the server, confirm the following: * There is adequate space on the server's /srv partition for the new set. * The .nbi folder's name is '''unique''' and '''contains no spaces'''. * The index number of the set is unique among sets already present, and between 1 and 65535 (index numbers above 4095 are normally used for images common across multiple !NetBoot servers). The Index of a set can be found in the `Index` key of the `NBImageInfo.plist` file within the .nbi folder. Connect to the NetBook server then use rsync to transfer the new !NetBoot set to the server, as shown below. If updating an existing set, the `--ignore-existing` option can be omitted to allow overwriting of files. {{{ user@netboot.server:~$ rsync -r --ignore-existing --progress @:.nbi /srv/netboot//sets/ }}} === Step 2: Replacing an existing NetBoot image by adhering to the naming scheme === Updating an existing !NetBoot image, i.e. 10.9.1 to 10.9.2, does not require a major change in the DHCP service configuration (see below) as long as one sticks to a few conventions: 1. The .nbi folder's name is `Orchard-NetBoot_--.nbi`, i.e. `Orchard-NetBoot_10.8.5-12F45-23.nbi` (23rd revision of the 10.8.5 !NetBoot image). The image name can be created using the following convenient command in the __running__ !NetBoot systen: {{{ NSMS_REVISION=1; echo "Orchard-NetBoot_$(sw_vers -productVersion)-$(sw_vers -buildVersion)-${NSMS_REVISION}.nbi" }}} 1. The `NBImageInfo.plist` within the .nbi folder has to contain the following values: * `Index` must match the designated index for the major OS: * 1024 = 10.10 (image id 01:00:04:00) * 2048 = 10.9 (image id 01:00:08:00) * 2096 = 10.8 (image id 01:00:10:00) * `Name` must match the user friendly display string ''Orchard !NetBoot '': * Orchard !NetBoot 10.10 * Orchard !NetBoot 10.9 * Orchard !NetBoot 10.8 * `Description` should contain the same information as the .nbi folder name and potentially a date the image was last updated and by whom. 1. The [[https://svn.oucs.ox.ac.uk/groups/nsms/config/puppet/dist/new-toy.orchard.ox.ac.uk/etc/dhcp/dhcpd.conf|dhcpd.conf]] needs replacing the `filename` and `option root-path` in the relevant `if option BSDP.selected_boot_image_id` section. Please note that the `dhcpd.conf` is under version control and managed by puppet. ==== Directory Structure of a .nbi folder ==== For reference: the directory structure of every .nbi folder should be as follows {{{ Orchard-NetBoot_10.9.1-13B42-1.nbi/ Orchard-NetBoot_10.9.1-13B42-1.nbi/NBImageInfo.plist Orchard-NetBoot_10.9.1-13B42-1.nbi/i386 Orchard-NetBoot_10.9.1-13B42-1.nbi/i386/booter Orchard-NetBoot_10.9.1-13B42-1.nbi/i386/PlatformSupport.plist Orchard-NetBoot_10.9.1-13B42-1.nbi/i386/x86_64 Orchard-NetBoot_10.9.1-13B42-1.nbi/i386/x86_64/kernelcache Orchard-NetBoot_10.9.1-13B42-1.nbi/NetBoot.dmg }}}. === Step 3: Reconfiguring the DHCP Service (optional) === Once the new !NetBoot set is on the server, we must add its information to the DHCP server configuration in order for it to be offered to clients. We use the ubiquitous [[http://www.isc.org/downloads/dhcp/|ISC DHCP]], which has its configuration file located at `/etc/dhcp/dhcpd.conf` on the server. However, the file is managed using Puppet, so you must instead edit the file `/config/puppet/dist//etc/dhcp/dhcpd.conf` in the NSMS Subversion repository. Instructions on updating a config file via Puppet can be adapted from the Apache config instructions given [[PuppetReference/ChangeWebConfig|here]]. Please use a copy of the existing `dhcpd.conf` file to follow along with the documentation that follows: The rules set up in the configuration file are consulted every time the DHCP server receives a DHCP packet from a client. If the configuration file specifies a test to be performed on a parameter, it is implied that that this parameter is one found in the packet received by the server, which is being acted upon. If a parameter is simply indicated with a value, it is implied that this parameter will be included in the packet that may subsequently be sent back to the client. Our `dhcpd.conf` file uses a custom class to handle packets containing BSDP data: `class "Apple-i386-NetBoot"`. Changes to the file that we will make to add, remove, or modify !NetBoot set offerings will all be confined to this class. However, we rely on settings elsewhere in the file to instruct the server to ignore packets from unknown hosts and, by omission, to do nothing with packets representing requests for IP addresses and other standard network configuration information (since we rely on the University's main DHCP servers for this). The rules defined in our `Apple-i386-NetBoot` class are therefore consulted only if the received packet has not been disqualified by the conditions mentioned above, and if other parameters of the packet (its `vendor-class-identifier`) identify it as being from an Intel Mac requesting !NetBoot information. The rules are further enclosed in an if-statement that ensures that the DHCP packet is of the correct type. If the statement's test is successful, the DHCP server is instructed to label its returned packets with a related `vendor-class-identifier` value so that the client can recognise them as BSDP responses. Now, we've reached the main rules. These rules are divided into two sections, each enclosed by an if-statement with mutually exclusive conditions, for the two types of queries and responses the server can handle: 1. `if option BSDP.message_type = 1` 1. `if option BSDP.message_type = 2` ==== 'LIST' Section Overview: ==== `BSDP.message_type = 1` indicates a BSDP `LIST` query (from the client) or response (from the server). These are encapsulated within DHCP `INFORM` and `ACK` packets, respectively. `LIST` refers to a listing of available !NetBoot sets for the client's platform (platform being more of a historical concern for handling PowerPC and Intel Macs differently). Thus, the query from the client is a request for the list !NetBoot sets it may use, and the response from the server provides that information. In more detail, the server's response contains the following parameters: * The IP of DHCP server that is responding to the BSDP LIST query * The server's priority (relevant should more than one !NetBoot server respond to the client - a server may adjust its priority based on load) * The ID of the default !NetBoot set * (optionally) The ID of the last !NetBoot set the client chose on this server, if the server has this information on record. * The list of available !NetBoot sets As you can see from the way these parameters are set in the configuration file, we are in some cases using static values for certain parameters that would be dynamically generated by a 'real' (Mac OS X Server based) Mac !NetBoot server: for example, load-based priority adjustment, or storage of previous !NetBoot choices by the client. For our purposes, however, this is not much of a problem. ==== 'SELECT' Section Overview: ==== `BSDP.message_type = 2` indicates to a BSDP `SELECT` message from the client or response from the server (again encapsulated within DHCP `INFORM` and `ACK` packets, respectively). The client's `SELECT` message contains the !NetBoot set it has chosen from the server's `LIST` response, and the server's subsequent `SELECT` response contains the following parameters which enable the client to find the resources to boot from its selected set: * A confirmation of the identity of the selected !NetBoot set * The IP address of the TFTP server with the necessary boot components for that !NetBoot set * The complete path to the `booter` file on the TFTP server * The URL of the disk image for the system disk of that !NetBoot set * The Computer Name to be used by the !NetBooted Mac while it is running. With this information, it should be somewhat more clear as to what we are doing when we edit the `dhcpd.conf` file to add, modify, or remove !NetBoot sets. ==== 'LIST' Section Editing: ==== Let's go back to the first section, where we deal with BSDP 'LIST' queries: There are three parameters we may want to modify here, with somewhat self-explanatory names, but cryptic values: * `BSDP.default_boot_image_id` * `BSDP.selected_boot_image_id` * `BSDP.boot_image_list` Each 'boot image ID' (either 'default' or 'selected') is a sequence of 4 bytes separated by colons, written in hexadecimal. The first byte indicates the type of image contained in the !NetBoot set ('1' for an OS X Client OS for !NetBoot rather than !NetInstall), the second byte is unused ('0'), and the last two bytes represent the !NetBoot set's Index number. For example, the boot image ID `01:00:00:7B` can be broken down as follows: || `01:` || `00:` || `00:7B` || || Mac OS X Client system for !NetBoot || (unused byte) || The !NetBoot set's Index is 123 (decimal), which is 7B in hexadecimal || Incidentally, it should now be clear why !NetBoot set Index numbers have a range of 1 through 65,535. With this information, we can set the `BSDP.default_boot_image_id` and `BSDP.selected_boot_image_id` parameters, if we are changing the default !NetBoot set. The two values should be the same - remember, we're faking some Mac OS X server functionality here. You can convert decimal to hexadecimal using Calculator.app, or in the Terminal with the following command: {{{ printf '%x\n' }}} The hexadecimal parameter values are not case sensitive. The `BSDP.boot_image_list` parameter, which we look at next, is a bit more of a headache. While the use of a custom option space for !NetBoot parameters allows us to specify most values neatly and avoid much hex wrangling, we can't avoid it with this one. It represents the list of available !NetBoot sets, structured as follows: || '''Image ID for default set''' : Length of name in bytes, as hex : Name in hexadecimal ASCII : '''Image ID for first alternate set''' : Length of name in bytes, as hex : Name in hexadecimal ASCII : '''Image ID for second alternate set''' : Length of name in bytes, as hex : Name in hexadecimal ASCII : '''And so on...''' || The following command can be used to obtain the length of the name as well as its hexadecimal representation: {{{ echo | perl -e 'while (<>){s/(.)/sprintf("%X:",ord($1))/ge;my $len=length()/3;s/:$//;print "\n Length (hex): ".sprintf("%02X",$len)."\n Name (hex): $_\n";}' }}} The name should (but doesn't have to) correspond to the value of the `Name` key in the set's `NBImageInfo.plist` file. Spaces are acceptable in the actual name, although not in the name of the .nbi folder. As an example, suppose we had two sets, as follows: || || '''Default Set''' || '''First Alternate Image''' || || Index: || 123 || 456 || || Type: || OS X Client, for !NetBoot || OS X Client, for !NetBoot || || Name: || 'Orchard Primary 10.8.4 !NetBoot' || '10.9.0 !NetBoot for Testing Only' || The correct value for the `BSDP.boot_image_list` parameter would then be: {{{ 01:00:00:7B:1E:4F:72:63:68:61:72:64:20:50:72:69:6D:61:72:79:20:31:30:2E:38:2E:34:20:4E:65:74:42:6F:6F:74:01:00:01:C8:1F:31:30:2E:39:2E:30:20:4E:65:74:42:6F:6F:74:20:66:6F:72:20:54:65:73:74:69:6E:67:20:4F:6E:6C:79 }}} or, broken up to be a bit more comprehensible, and with comments: {{{ # Default boot image ID: 123 01:00:00:7B: # Name length: 30 chars 1E: # Name: Orchard Primary 10.8.4 NetBoot 4F:72:63:68:61:72:64:20:50:72:69:6D:61:72:79:20:31:30:2E:38:2E:34:20:4E:65:74:42:6F:6F:74: # First alternate boot image ID: 456 01:00:01:C8: # Name length: 31 chars 1F: # Name: 10.9.0 NetBoot for Testing Only 31:30:2E:39:2E:30:20:4E:65:74:42:6F:6F:74:20:66:6F:72:20:54:65:73:74:69:6E:67:20:4F:6E:6C:79 }}} ''Please do not attempt to modify the configuration until this makes sense to you, and you can produce exactly the same sequence of hexadecimal numbers from the example parameters!'' Breaking up the sequence into multiple lines and adding whitespace and comment lines is very helpful in keeping the configuration file comprehensible, and does not affect how it is parsed by the DHCP server. ==== 'SELECT' Section Editing: ==== Now let's move to the second section, where we deal with BSDP 'SELECT' queries: Here, we use one if-statement with a single branch for each offered set. The test is a match of the `BSDP.selected_boot_image_id` from the client's `SELECT` message. Make sure your if-statement's condition matches the boot image ID for the set you're adding or modifying. Luckily, setting the values of the parameters here is easier. There are four that we must set: * `BSDP.selected_boot_image_id` * `filename` * `root-path` * `BSDP.machine_name` `BSDP.selected_boot_image_id` has already been covered. This is returned to the client here to confirm that the information given is for their selected set. `filename` is the TFTP path to the `booter` file on the TFTP server. This parameter, unlike the others we've covered, should not be specified with the `option` keyword. `root-path` is the complete HTTP URL of the `NetBoot.dmg` file of the set. `BSDP.machine_name` is the Computer Name the client should use while !NetBooted. ---- == Testing == It is possible to test the `dhcpd.conf` file for syntax errors with the following command on a machine with ISC DHCP Server installed: {{{ /usr/sbin/dhcpd -q -t -cf }}} However, it is perhaps more likely that an error will occur within the vendor specific options we have configured, which will not be picked up by the DHCPD parser. Instead, after making configuration changes, check by Option-booting a recent Mac to ensure that it is correctly displaying the expected sets, and that each can be selected and booted successfully, and that the default image is correctly obtained when booting with the N-key depressed.