To read original PDF of the print article, click here.
Internet Appliance Design
|Also in this issue:
Open Source Point/Counterpoint:
The Internet Protocol is just that: the protocol upon which the Internet is built. It is also the IP of TCP/IP and UDP/IP fame. Several types of network interfaces may lie below the IP layer (serial, Ethernet, ATM, and so on) and a variety of upper-layer protocols may run on top of it (TCP, UDP, and so on). IP is, therefore, the one constant, the glue that holds the network together. This month's column is about the IP layer and my implementation of it. But first, I need to make a quick correction and give my stack a name.
An outdated copy of the source code for one address resolution protocol (ARP) function managed to slip through the cracks and into my column, “Mid-Year's Resolutions,” (July 2000, p. 43). The misprinted function was named NetArpAddEntry() and shown in that month's Listing 5. The call to NetArpInit(n) shown there should have been to NetArpFlush(). I had later changed the name of the cache initialization function, for clarity's sake. It seems to make more sense to flush a cache more than once than to re-initialize it. The parameter n passed to NetArpInit() should not have appeared in the magazine either. That was something I had added for my own testing of the stack. To allow two UDP/IP stacks to share the same Win32 process space it was necessary that they each have their own ARP cache.
I've decided to name the open source UDP/IP stack I've been developing µC/Net. This name sounds nice =for two reasons. First, with this stack I'm trying to bring internet connectivity to low and mid-range microcontrollers. Also, the name µ/Net works well alongside the µC/OS-II operating system, to which I'm currently porting the stack.
The IP layer's main role is to provide for the routing of packets from one IP address to another. In other words, it can connect any machine on the Internet with any other, regardless of physical location. One of the communicating machines may be connected to an Ethernet LAN at a company in China, while the other could be a laptop in Maine that has just connected with a modem and a PPP link. Because of IP, their physical locations and underlying network interfaces don't matter. The IP layer software on each machine and similar software within routers and switches on the Internet backbone make this end-to-end communication possible.
The result of all this is a logical connection between two computers. This connection can be used to send and receive all kinds of data. For example, the two computers could exchange Web pages via HTTP over TCP, SNMP packets over UDP, telnet sessions, voice over IP, or any other type of data that can be sent in a stream of one or more digital packets.
Because so many types of data can be exchanged over IP, the Internet Protocol is full of features that are only used by a subset of connected hosts. In fact, the protocol itself is overdue for an upgrade from the current version, called IPv4, to the more capable IPv6. (For more on IPv6 and the coming changeover, see the article by Stephen Harpster in this month's Internet Appliance Design section.) Since IPv6 is not yet widely deployed and even many of the IPv4 features are not necessary for a lot of applications, my implementation of the IP layer includes only a sort of bare-minimum of functionality to satisfy IPv4-style communications. Most commercial TCP/IP stacks for sale in the embedded systems marketplace are more complete.
To get a feel for what is and isn't supported, let's look at the data structures and constants defined in Listing 1. Of particular note here is that the NetIpHdr structure defines a header of a fixed size. In truth, the length of this header may be extended in 32-bit increments to support some less commonly used IP features. However, µC/Net never uses any of the special features that these extensions allow. So variable length IP headers are not generated by the stack and it will reject any incoming IP packets that have a header of length other than 20 bytes.
The first field in any IP header, ver_hlen, gives the protocol version (4 for IPv4) in the first nibble and header length (five 32-bit words for the fixed size header) in the second nibble. All of the IP packets sent by µC/Net will, therefore, have this field set to the constant value 0x45. Similarly, it is a requirement that all packets received by the stack have that same value in ver_hlen.In addition to this restriction, several other features of IP are either unsupported by µC/Net or the support is significantly scaled back. The only one of these that is likely to affect you is their lack of support for fragmentation and reassembly.
Fragmentation and reassembly
A more complete IP layer implementation would include a feature called fragmentation and reassembly. The idea that underlies this feature is simple: payloads too large to be sent over the physical network all at once should be split up across multiple frames (fragmented) and reassembled at the receiving end. The IP header field fragment can be used by the stacks on the two ends to coordinate this process.
Unfortunately, the implementation of fragmentation and reassembly is extremely cumbersome. Adding just this one feature to the IP layer would approximately double the amount of code (and code complexity) of the entire µC/Net stack! For that reason, I've made the decision not to support fragmentation and reassembly at all at this time. If the UDP layer attempts to send a packet that is too large, the IP layer will simply truncate the data to IP_DATA_LEN bytes and send that instead. If a remote computer fragments and then sends a large packet to a computer running µC/Net, the incoming data will appear to the client or server application as though it were actually multiple unrelated packets of information, rather than one really large one. (A poorly coded application could get confused by this, so beware.)
Sending and receiving
Some fields of the IP header are filled in by the UDP layer above. Specifically, the UDP layer fills in the IP header's source and destination IP addresses, which it combines with the UDP header and payload to compute a UDP checksum. The UDP sending function then passes the entire packet to NetIpSnd(). After filling in the remaining fields of the IP header, NetIpSnd() computes its own checksum(of the IP header contents only) and passes the completed IP packet on to the network driver, to be sent out over the physical network.
The NetIpRcv() function, shown in Listing 3, handles incoming IP packets. This function is called from within the context of a task that's part of the network driver. The driver task becomes active whenever a new packet arrives over the network. It then sees what type of packet it is (ARP, IP, and so on) and routes it to the appropriate function. NetIpRcv() processes all incoming IP packets.After checking the IP version, header length, and checksum, each incoming IP packet is routed to the layer above. If it is a UDP packet, NetUdpRcv() is called. If it is a TCP packet and TCP support is included, NetTcpRcv() is called instead.
Next month I'll tell you everything you ever wanted to know about the UDP layer that lies above this one. And, of course, I'll share my implementation with you. In the meantime, stay connected…
Michael Barr is the editor in chief of Embedded Systems Programming. He holds BS and MS degrees in electrical engineering from the University of Maryland. He is the author of the book Programming Embedded Systems in C and C++ (O'Reilly & Associates). Michael can be reached via e-mail at mbarr @ netrino.com.