Internet Working - Embedded.com

Internet Working



To read original PDF of the print article, click here.

Internet Appliance Design

Also in this issue:

Open Source Point/Counterpoint:

Internet Working

Michael Barr

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.

IP freely
As we've seen, the presence of a network interface like an Ethernet controller in a product makes it possible to send packets of data from one computer to another. To send the data, however, a destination hardware address must be attached to the packet before it is streamed out onto the network. ARP provides a mapping service between the hardware address of the destination computer and its IP address. So despite the fact that they are not part of the IP layer, both a network interface and ARP are required for network communications.

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.

Listing 1: Data structures and constants for the IP layer
/* * IP Header */typedef struct{  INT8U   ver_hlen;   /* Header version and length (dwords). */  INT8U   service;    /* Service type. */  INT16U  length;     /* Length of datagram (bytes).*/  INT16U  ident;      /* Unique packet identification. */  INT16U  fragment;   /* Flags; Fragment offset. */  INT8U   timetolive; /* Packet time to live (in network). */  INT8U   protocol;   /* Upper level protocol (UDP, TCP). */  INT16U  checksum;   /* IP header checksum. */  INT32U  src_addr;   /* Source IP address. */  INT32U  dest_addr;   /* Destination IP address. */} NetIpHdr;#define IP_VER_HLEN    0x45#define IP_HEADER_LEN  sizeof(NetIpHdr)#define IP_DATA_LEN    (MTU - IP_HEADER_LEN)/* * IP Packet */typedef struct{  NetIpHdr       ipHdr;  unsigned char   data[IP_DATA_LEN];}

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
Ethernet frames have a defined maximum size payload of 1,500 bytes each. This is termed the maximum transmission unit, or MTU, of the physical network. Included within that payload are the contents of the IP header, the UDP (or TCP) header, and the actual data. If all of the packets of data you want to send and receive fit within that maximum size-as is the case in the majority of embedded applications requiring only limited connectivity-µC/Net will do the job handily. If, however, you want to send even a single 1,501-byte frame, you'll run into trouble.

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.)

Listing 2: A function to send an IP packet
intNetIpSnd(NetIpPkt * pIpPkt, INT16U len) {    INT16U  ident;    /*     * Assign a unique ID to the outgoing packet.     */    // Enter critical section here.    ident = gNextPacketID++;    // Leave critical section here.    /*      * Fill in the remaining IP header fields.     * (Some fields were pre-filled by the UDP layer.)     */    pIpPkt->ipHdr.ver_hlen   = IP_VER_HLEN;    pIpPkt->ipHdr.service    = 0x00;          pIpPkt->ipHdr.length     = htons(len);     pIpPkt->ipHdr.ident      = htons(ident);    pIpPkt->ipHdr.fragment   = 0x00;    pIpPkt->ipHdr.timetolive = 0x10;    /*      * Compute and fill in the IP header checksum.      */    pIpPkt->ipHdr.checksum = NetIpChecksum((INT16U *)     pIpPkt, IP_HEADER_LEN);    /*      * Forward the IP packet to the network interface driver.      */    return (NetPhySnd(htons(PROTO_IP),     (unsigned char *) pIpPkt, len));}

Sending and receiving
By now you must be wondering what IP features I have implemented. Ultimately, the IP layer is just responsible for sending and receiving IP packets. Listing 2 shows the code for NetIpSnd(), which does the sending. The IP packets resulting from this function call should make sense to any IPv4 system to which they are sent. That's the beauty of a more limited implementation of the protocol: it's small, easily coded and understood, and yet-because it implements a subset of the full protocol-can communicate with any system on the network.

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.

Listing 3: A function to receive an IP packet
intNetIpRcv(NetIpPkt * pIpPkt){    INT16U  checksum;    /*     * Check IP header version and length.     */    if (pIpPkt->ipHdr.ver_hlen != IP_VER_HLEN)    {        /*         * Unsupported header version or length.         */        return (NET_ERROR);    }     /*     * Move the IP header checksum out of the header.     */    checksum = pIpPkt->ipHdr.checksum;    pIpPkt->ipHdr.checksum = 0;    /*     * Compute checksum and compare with received value.     */    if (checksum != NetIpChecksum((INT16U *) 	pIpPkt, IP_HEADER_LEN))    {                    return (NET_ERROR);   //Bad checksum    }    /*     * Route the packet to the appropriate Layer 3 protocol.     */    switch (pIpPkt->ipHdr.protocol)    {      case PROTO_UDP:        return (NetUdpRcv((NetUdpPkt *) pIpPkt,                           ntohs(pIpPkt->ipHdr.length) - IP_HEADER_LEN));#if defined (NET_TCP_EN)	      case PROTO_TCP:        return (NetTcpRcv((NetTcpPkt *) pIpPkt,                           ntohs(pIpPkt->ipHdr.length) - IP_HEADER_LEN));#endif      default:      return (NET_ERROR);	// Unsupported protocol    }}   /* NetIpRcv() */

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.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.