For our project, we implemented the UDP/IP and ARP protocols in hardware. Our design as implemented accepts packets from an Ethernet cable connected to the Altera DE2 board. On the board, a packet gets routed from the Ethernet cable through the DM9000A hardware module given by Altera and then through the NiosII CPU.
Receiving packets from the NiosII:
recv_buffer module:
When receiving packets, we assume that the entire packet is not sent continuously from the NiosII. To address this issue, we interface our recv_buffer module with the NiosII by implementing a handshaking protocol. When a packet chunk is received, the recv_buffer module will decode whether the packet is IP or ARP and then pass the data onto the appropriate module (either arp_recv or IP_recv). After receiving and sending the data packet, the recv_buffer module sends an acknowledge back to the NiosII and waits for the data chunk to come from the NiosII.
IP_recv module:
The IP_recv module does several things. It accepts data from the recv_buffer when the incoming valid bit is set high. However, the data does not simply get routed through this module. An IP packet consists of both a header and data section. At least the first five words of an IP (we are assuming that the packets received are IPv4 version) packet consist of the header of the packet. The first word consists of the total length of the packet. The second word contains the IP identification number. The third word specifies the protocol being used and the checksum, a value that ensures that the packet is correct. The fourth and fifth words are the source and destination IP addresses. The header of an IP packet may be of variable size due to an options field, however for simplicity, our hardware module assumes that there are no options attached to the packet headers received. The data section comes after the header and may be of variable length. Information on the structure of the IPv4 packet was obtained from Wikipedia.
The IP_recv module decodes the header of the IP packet and ensures the packet's validity by calculating its checksum. When the checksum is calculated to be valid, the module will send the packet (data only, no header) and the destination IP address to the udp_recv module. Because we were not guaranteed a constant stream of data from the recv_buffer, the module was written to be able to accept a non-continuous data packet.
udp_recv module:
Assuming that data is send using the UDP protocol, within the data section of an IPv4 packet, there exists a subset data and header sections. The first two words of the data section are considered the header of the UDP packet with the rest being the UDP data. The first word of the UDP header consists of the source and destination ports for the data. The second word contains the length and checksum of the UDP packet. Because does not guarantee the reliability of the packet being sent, we do not check the checksum of the UDP header. The remaining words are the data of the packet. Information about UDP was obtained from Wikipedia.
The udp_recv module takes the data coming from the IP_recv module, decodes the destination address and forwards that to the application layer that we are attaching to our hardware. The header is dropped and only the data is forwarded to the application layer.
Sending packets out through Ethernet:
udp_send module:
When we want to send out a packet of data, it needs to be encapsulated with the various protocol headers. First is the UDP header, then the IP header (at which point it becomes a packet), and then the NiosII software adds on the MAC header. The udp_send module does the first step of this. It takes the data being sent by the application and puts the 8-byte UDP header onto the front of it. This requires buffering 2 words inside the module so the 2-word-length header can be transmitted to IP_send before the actual data. Also, in the header, the udp_send module inserts the main feature of UDP, which is the port number. This port number is defined by the application running on the DE2 and is passed into the udp_send module.
IP_send module:
When an application sends data out, the udp_send module will send that data to the IP_send module. In this case, because we are generating the traffic coming from the application, we assume that the data is continuous from the source. Before the data can be passed back onto the Ethernet, the IP_send module must attach a header to the data coming from the udp_send module. Of the header fields, three are not hard-coded and must be set every time a new packet of data is received from the udp_send module. The total length flag is variable with the data length. To calculate this field, the udp_send module passes the data length to the IP_send module. The Identification field is set to 0 initially and increments by one every time a new packet is sent so that each packet has a unique identification number. The header checksum is calculated by summing the one's complement of each half-word in the header excluding the checksum and then is one's complemented itself. This calculation is greatly simplified however due to the fact that five of the ten half words that make up the header can be used to calculate a checksum base because the fields do not change. The packet, with header and data, can be sent out of the IP_send module in parallel with the calculation of the header. The final output of the IP_send module is an IPv4 packet.
send_buffer module:
The send buffer sits between the protocol transmit modules and the CPU. It serves to multiplex the CPU between the ARP send and IP send in case they both are trying to use it, and it handles all the handshaking communication required to communicate between the hardware and the CPU. It is also responsible for looking up IP to MAC translations for outgoing UDP packets. If it cannot find the translation in the cache, it will send out an ARP request for the given IP address. It will then wait for the reply, timing out and dropping the packet if it takes too long. It also contains two other state machines, one for IP and one for ARP, to take in the data from the hardware and buffer it all. After a whole packet is buffered, it tells the CPU the data is ready. The CPU reads all the data from the buffer then tells the send buffer it is done so that it knows it may update the buffer with the next packet.
ARP implementation
In order to fully implement IP, we also had to implement ARP as well. ARP, or Address Resolution Protocol, is a way to find out a remote host's MAC address if all that is known about it is its IP address. If a MAC address is unknown, the DE2 can hold off on sending the UDP/IP packet and send out an ARP request packet, which should then be followed by an ARP reply packet coming back to us with the requested MAC address information enclosed. If not, our system times out and we continue on to the next packet.
arp_rcv module:
The arp_rcv module in our system takes in the ARP packets, which have been parsed as such by the NiosII code and the send_buffer, one 32-bit word at a time. It then unwraps them, determines where they are coming from and puts the source MAC and IP addresses in the mac_cache (aka ARP table). It also determines if they are request or reply packets. If they are reply packets, the arp_rcv module does nothing more after storing the addresses in the mac_cache (the send_buffer handles the rest). If the incoming ARP packets are requests for our own MAC address, the arp_rcv module notices this from the OPER field in the ARP packet. It then sends a notice to arp_send to send a reply packet back out to the remote host, including our own IP and MAC addresses.
arp_send module:
The arp_send module sends out ARP packets one word at a time when the send_buffer is ready for them, as denoted by the send_buffer_ready bit that is an output from send_buffer and an input into arp_send. It sets arp_valid high when it is sending to tell the send_buffer that the data is part of a valid ARP packet. Once one packet begins to be sent, another flag called clear_to_send is pulled low to tell any new ARP packets that need to be sent (both requests and replies) to be buffered until the next available time to send (when the current packet is done being sent, and when the send_buffer is ready again).
There are two buffers for outgoing ARP packets: one for requests and one for replies. If a packet needs to be sent but is not ready to be sent, it is put in its respective buffer. If the arp_send module finds out that a request packet and reply packet both need to be sent at the same time, it will buffer the reply packet and send the request packet first. The state machines in arp_send and send_buffer ensure that only one ARP packet of each type is sent to the arp_send module at a time so there is no buffer overrun and no packets are lost.
DM9000A
The DM9000A is the on-board Ethernet controller. The interface to it is complex and designed to be interfaced to software. The DE2 Board CD comes with an example program to interface to the DM9000A, which we used as a basis for our program. The example only deals with raw Ethernet frames, so we extended it to support network and transport protocols on top of this. The receive interrupt now takes the Ethernet header and checksum off the packet and checks to see if it is an ARP or IP packet. It will also remove trailing padding characters, as it is possible to send a small IP packet that does not meet the 64 byte Ethernet minimum packet size. It then sends it to the appropriate hadrware to be parsed. The main loop was changed from constantly sending the same packet to waiting for our hardware to request to send a packet. It then adds the Ethernet header, which consists of the source and destination MAC address, and then pass it on to the DM9000A. The DM9000A will take the frame and automatically calculate & append the checksum to it, and pad it if necessary.
PC Communication
In order to test our application we attached it to a laptop with a standard Ethernet cable. We configured the network by assigning static IP addresses of 192.168.1.2 and 192.168.1.3 to the board and PC respectively, setting the subnet mask to 255.255.255.0, and no default gateway. Running Wireshark (formerly Ethereal), which is a packet sniffer, we were able to watch all traffic on the Ethernet port. (The packet sniffer was not used at all on Cornell's CIT network, only on our closed system.) Along with the ARP and UDP packets we were generating, there was also other packets being generated by the operating system. In order to generate UDP packets to send to the board we used the Python programming language. It has built in networking libraries which make it extremely easy to generate UDP packets. With only a few lines of code (found on the UDP Wikipedia article) we can open a connection to the client and send it data on any UDP port.