Dealing with Software Expectations

In an embedded environment, making wise choices about the software that runs on your device comes somewhat naturally—you’re very concerned about how much space your software takes up, so you probably don’t have a lot of extra servers clogging up the boot flash anyway.

But configuration is a different story. In general, configuring a Linux system is difficult. Each service usually wants its own configuration file. Each configuration file has a different syntax. Many configuration files want to live somewhere in the /etc tree, but others don’t. For all of these reasons, replicating a Linux system is especially difficult—where do you get all of the files?

Configuring an embedded system addresses all these issues and adds a few of its own. Each program that runs on the embedded device expects to find its configuration file in a certain location in the directory tree. Some locations may not be writeable because they live in a read-only filesystem. Others may be in a RAM filesystem, so any changes will be lost during the next reboot.Writing several configuration files to flash memory implies that a filesystem must be on that flash, which adds complexity.

Symlinking Configuration Files

The solution to configuration files being spread across many directory trees is fairly easy; for each configuration file, create a symbolic link pointing to a file in a single directory that houses all the configuration files. A good name for this directory is /config.

For example, let’s say that your application needs to be able to change the values stored in the resolv.conf and syslog.conf files as part of its normal operations. Both of these files normally reside in the /etc directory. However, the /etc directory also contains many files that you’ll never need to change but do need to exist, such as the hosts and services files. Instead of storing the contents of resolv.conf and syslog.conf in the /etc directory, you can simply create symlinks called /etc/resolv.conf and /etc/syslog.conf that point to /config/resolv.conf and /config/syslog.conf, respectively.

This concept is quite powerful. The /config directory can then be mounted on a different device from the rest of the root filesystem. This way, the root can consist of a smaller read-only filesystem, and the /config filesystem can live in flash and be read/write. Each configuration file looks like it lives where the server software expects it to live, but in reality it can be changed at will by the configuration software.

The Embedded Linux Workshop takes this concept one step further. Instead of having multiple configuration files in one read/write directory, there is a single configuration file that can either live in a single read/write directory or, using a simple kernel driver, in raw flash memory. There are several advantages to centralizing the configuration information to a single file:

  • You don’t have to have a full flash filesystem to store a single file.

  • Your configuration software doesn’t have to understand how to modify all the different types of configuration files in existence. Most configuration files are meant for humans to modify, not computer programs, so they’re difficult to change programmatically.

  • A single configuration file makes replicating an embedded device simple—copy a single file from one machine to the other.

  • A simple name=value structure works for most situations. This structure is easily understandable by humans and easily parsable and modifiable by computers.

Resolving Software Expectation Conflicts

There’s still one problem with this approach; the software programs that require the configuration files expect them to be in a certain form.The syslogd program, for example, won’t know what to do with the name=value syntax used by the Embedded Linux Workshop’s single configuration file.

There are basically two approaches to resolving this problem:

  • Modify the programs so that they can use the single configuration file directly.

  • Using the information in the single Embedded Linux Workshop configuration file, create the configuration file(s) that the program expects to see.

For the most part, I use the second approach for the Embedded Linux Workshop. I believe that the fewer changes made to the individual components of an embedded project (syslogd, for example), the better. Here’s why:

  • If it was working when you got it, and you didn’t touch it, it should still work in the field.

  • Changing software such as syslogd is difficult and error-prone if you’re not already familiar with the software. However, creating the syslogd.conf file out of name=value pairs from the single configuration file is relatively easy.

  • If you change the syslogd software to read its configuration information directly from the single configuration file, that’s a substantial change that you’ll have to keep track of forever. If you need to upgrade to a later version of syslogd in the future, you’ll have to create and apply a patch to the new version, fixing any problems in software you may not have seen for a while. On the other hand, if you use the second approach—creating the configuration file(s) that the software expects to see—and the configuration file syntax has not changed in the new version, you have no work to do when you upgrade versions.

There are a few drawbacks to building the configuration file(s) that the software expects to see rather than changing the software to use the single configuration file. First, your boot time will be slower than it could be. Probably not by much, but still—slower. Second, the custom configuration file(s) will take up more room on your embedded device. The software to create the application-specific configuration file and the configuration file itself both take up room that may be extremely valuable, depending on the size of your embedded device. You can normally remove the application-specific configuration file once the software that needs it is finished reading it, but doing so adds complexity to your startup code.

There is ample precedent for using a single configuration file for the entire computer. The most well-known example of this approach is the Registry in Microsoft Windows. Even with all its shortcomings, the Windows Registry has made life easier for developers and users alike by moving all configuration information to a central, searchable location. To get an idea of how the single-configuration file concept works in practice, let’s take a look at two examples from the Embedded Linux Workshop: the network setup code and the resolver setup code.

When the machine in this example boots, it stays in initrd mode, so the first user space code to run is a script named linuxrc. The job of this script is to set up the runtime environment. Part of that setup code calls each of the startup scripts for the various packages, much like the /etc/rc.d/rc3.d scripts in a Red Hat system. Listing 3.1 shows the code within linuxrc that runs the startup scripts.

Listing 3.1. Part of the /linuxrc Script
. /mnt/envi 
for x in `(cd /etc/rc;echo S*)`;do 
    echo Starting $x... 
    /etc/rc/$x 
done 

The first line of the script loads the configuration file. The configuration file is set up so that any shell script that needs its contents can simply source it in with the dot (.) command, and all the name=value pairs are loaded into shell variables. The rest of the linuxrc script finds the names of each of the files in the /etc/rc directory that begin with S, and runs them.The two scripts that we’re going to look at are S14resolv (see Listing 3.2) and S15network.

Listing 3.2. The S14resolv Script
1  #!/bin/sh 

2  ###################################################################### 
3  # Set up the DNS resolver 
4  ###################################################################### 
5  [ -z "$RESOLV_CONF" ] && RESOLV_CONF=/etc/resolv.conf 
6
7  rm -f $RESOLV_CONF 
8  [ -n "$RESOLV_IP0" ] && echo "nameserver $RESOLV_IP0" >> $RESOLV_CONF 
9  [ -n "$RESOLV_IP1" ] && echo "nameserver $RESOLV_IP1" >> $RESOLV_CONF 
10 [ -n "$RESOLV_IP2" ] && echo "nameserver $RESOLV_IP2" >> $RESOLV_CONF 
11 [ -n "$RESOLV_IP3" ] && echo "nameserver $RESOLV_IP3" >> $RESOLV_CONF

The S14resolv script’s job is to set up the Linux system so that applications running within the embedded box are able to resolve network names using DNS. It does so by first deleting (line 7), and then building up (lines 8–10) the /etc/resolv.conf file from values in the single configuration file. To make all of this work, the configuration file has a line that looks like this:

export RESOLV_IP0="4.2.2.1" 

After the S14resolv script completes, the /etc/resolv.conf file has a single line:

nameserver 4.2.2.1 

If we had named RESOLV_IP1, RESOLV_IP2, or RESOLV_IP3 within the configuration file, the /etc/resolv.conf file would have had more nameserver lines to reflect the multiple DNS name servers we can access.

Next, we’ll look at a much more complex example, S15network (see Listing 3.3). The S15network script does most of the work when bringing up the network:

  • Sets up the localhost.

  • Can support up to 10 network connections.

  • Allows for routing and masquerading between networks.

  • Begins the setup of ipchains for firewall rules.

  • Optionally sets up a default route.

  • Uses the newer ip command instead of ifconfig/route to conserve space.

Listing 3.3. The S15network Script
 1  #!/bin/sh 
 2  ###################################################################### 
 3  # Start IP networking 
 4  ###################################################################### 
 5 
 6  ###################################################################### 
 7  # conf: Configure a network interface 
 8  # $1   NETIF      (eth0) 
 9  # $2   NETIP      (192.168.0.1) 
10  # $3   NETNAM     (WAN/LAN/dmz/etc) 
11  # $4   NETMASK    (255.255.255.0) 
12  # $4   NETOPT     (MTU=1500 promisc) 
13  # $5   NETDEF     (""=no !""=ip address of gateway) 
14  ###################################################################### 
15  conf ( ) {
16    NETEN=$1; shift 
17    NETIF=$1; shift 
18    NETIP=$1; shift 
19    NETNAM=$1; shift 
20    NETMASK=$1; shift 
21    NETOPT=$1;  shift 
22    NETDEF=$1;  shift 
23    NETMASQ=$1; shift 
24    if [ -n "$NETIP" ] && [ "$NETEN" == "1" ]; then 
25       NETMASK=`ipcalc -m $NETIP $NETMASK`       # Calc deflt if no mask 
26       NETBITS=`ipcalc -t $NETIP $NETMASK` 
27       BROADCAST=`ipcalc -b $NETIP $NETMASK` 
28       NETWORK=`ipcalc -n $NETIP $NETMASK` 
29  
30       # bring up the nic 
31       ip address add $NETIP/$NETBITS broadcast $BROADCAST dev $NETIF 
32       if [ "$?" != "0" ]; then 
33          echo "$NETNAM ($NETIP-$NETIF): failed (ip address add)" 
34         return 1 
35       fi 
36       ip link set dev  $NETIF up 
37      if [ "$?" != "0" ]; then 
38          echo "$NETNAM ($NETIP-$NETIF): failed (ip link up)" 
39          return 1 
40       fi 
41       [ "$NETOPT" != ""]&& $NETOPT 
42 
43        # give it the default route 
44        if [ -n "$NETDEF" ]; then 
45           ip route add default via $NETDEF 
46           if [ "$?" !!= "0" ]; then 
47              echo "$NETNAM ($NETIP-$NETIF): failed (default route)" 
48              return 1 
49           fi 
50        fi 
51 
52        # Masquerade this network? 
53        if [ "$NETMASQ" == "1" ]; then 
54           ipchcnf del $NETNAM-masq 
55           ipchcnf add $NETNAM-masq -A forward -s $NETWORK/$NETMASK -j MASQ 
56        fi 
57 
58        # And we're up! 
59        echo "$NETNAM ($NETIP-$NETIF): up" 
60     fi 
61     return 0 
62  } 
63 
64  # Retry client's connect attempts 
65  # See: /usr/src/linux/Documentation/networking/ip_dynaddr.txt 
66  # ----------------------------------------------------------
67  echo "1" > /proc/sys/net/ipv4/ip_dynaddr 
68 
69  # Enable routing, but force specific forward rules 
70  # -----------------------------------------------
71  ipchains --policy forward REJECT 
72  echo "1" > /proc/sys/net/ipv4/ip_forward 
73 
74  # Configure global masquerading 
75  # ----------------------------
76  ipchains -S 7200 10 60 
77 
78  # Configure localhost 
79  # ------------------
80  #ifconfig lo 127.0.0.1 netmask 255.0.0.0 broadcast 127.255.255.255 
81  #route add -net 127.0.0.0 netmask 255.0.0.0 lo 
82  ip address add 127.0.0.1/8 broadcast 127.255.255.255 dev lo 
83  ip link set dev lo up 
84 
85  # Configure network 
86  # ----------------
87  conf   "$NETEN0" "$NETIF0" "$NETIP0" "$NETNAM0"  
88         "$NETMASK0" "$NETOPT0" "$NETDEF0" "$NETMASQ0" 
89  conf   "$NETEN1" "$NETIF1" "$NETIP1" "$NETNAM1"  
90         "$NETMASK1" "$NETOPT1" "$NETDEF1" "$NETMASQ1" 
91  conf   "$NETEN2" "$NETIF2" "$NETIP2" "$NETNAM2"  
92         "$NETMASK2" "$NETOPT2" "$NETDEF2" "$NETMASQ2" 
93  conf   "$NETEN3" "$NETIF3" "$NETIP3" "$NETNAM3"  
94         "$NETMASK3" "$NETOPT3" "$NETDEF3" "$NETMASQ3" 
95  conf   "$NETEN4" "$NETIF4" "$NETIP4" "$NETNAM4"  
96         "$NETMASK4" "$NETOPT4" "$NETDEF4" "$NETMASQ4" 
97  conf   "$NETEN5" "$NETIF5" "$NETIP5" "$NETNAM5"  
98         "$NETMASK5" "$NETOPT5" "$NETDEF5" "$NETMASQ5" 
99  conf   "$NETEN6" "$NETIF6" "$NETIP6" "$NETNAM6"  
100        "$NETMASK6" "$NETOPT6" "$NETDEF1" "$NETMASQ6" 
101 conf   "$NETEN7" "$NETIF7" "$NETIP7" "$NETNAM7"  
102        "$NETMASK7" "$NETOPT7" "$NETDEF7" "$NETMASQ7" 
103 conf   "$NETEN8" "$NETIF8" "$NETIP8" "$NETNAM8"  
104        "$NETMASK8" "$NETOPT8" "$NETDEF8" "$NETMASQ8" 
105 conf   "$NETEN9" "$NETIF9" "$NETIP9" "$NETNAM9"  
106        "$NETMASK9" "$NETOPT9" "$NETDEF9" "$NETMASQ9" 

The S15network script has three major parts:

  • Lines 15–62 comprise the conf() function.

  • Lines 64–83 set up various files in the /proc filesystem and initialize the firewall and localhost.

  • Lines 87–106 call the conf() function to set up each of the network interfaces.

Let’s trace the following sample configuration through the S15network script:

export NETEN0=1 
export NETNAM0="wan" 
export NETIF0="eth0" 
export NETIP0="192.168.0.9" 
export NETDEF0="192.168.0.1" 

export NETEN1=1 
export NETNAM1="lan" 
export NETIF1="eth1" 
export NETIP1="192.168.10.9" 

Lines 15–62 define the conf() function and are of no consequence until later in the script.

Lines 67 and 72 set different kernel flags by writing to special files in the /proc filesystem. Line 67 tells the kernel to retry the connection that caused a network session to activate—usually a PPP connection. Line 72 tells the kernel that it can forward packets between network connections. However, line 71 sets up the default firewall rule to not allow forwards between network connections. If any forwards are required, they must be set up later with specific firewall rules.

Line 76 sets up good defaults for masquerade timeouts.

Lines 82–83 set up the loopback interface .

Finally, we get to the configuration of the network interfaces. In our example, we have two network interfaces, eth0 (named wan) and eth1 (named lan). Because we only have two, only the first two calls to the conf() function (lines 87–88 and 89–90) are of any consequence.

The first call to conf() on lines 87–88 sets up the eth0 (wan) interface. Lines 16–23 extract the local variables from the function call. Line 24 tests to make sure that the interface is enabled and that an IP address is assigned. Lines 25–28 use an external C program, ipcalc, to calculate the network address, network mask, and network bits.

Lines 31–35 assign the IP address and network information for the interface, and then deal with any error(s) returned. Similarly, lines 36–40 actually bring up the link and check for errors.

Line 41 is a bit of a kludge. A previous evolution of this script used ifconfig/route instead of the ip command. The NETOPT variable was used to give ifconfig any extra parameters (such as an MTU setting). Since the syntax of the ip command is different from that of the ifconfig command, the same technique won’t work for both. I decided to just redefine the NETOPT as a separate command. With it, you can do whatever you need (such as set the MTU of an interface).

Lines 44–50 set up the default route, and lines 53–56 set up IP masquerading.

After studying the code, you may be a bit worried about security. For instance, the NETOPT variable simply runs a command—any command. Similarly, the NETDEF variable could be set to "some.ip.address; rm -rf /", which would blow away the machine on line 45. For the Embedded Linux Workshop, I’ve taken the stance that the data in the configuration file is trustworthy.Verification is done in the software that builds and/or modifies the configuration file, since this software must be secure anyway.

The second call to conf() sets up the eth1 (lan) interface.The specifics for the conf() function are very similar to those for the eth0 (wan) interface, except that the default interface is already set up, so the test on line 44 is not activated.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.45.162