[ell-i-developers] Outline and guide for IP/UDP testing

  • From: Pekka Nikander <pekka.nikander@xxxxxx>
  • To: "ell-i-developers@xxxxxxxxxxxxx" <ell-i-developers@xxxxxxxxxxxxx>
  • Date: Sat, 1 Mar 2014 08:01:24 +0200

In a few days we need to start testing the IP/UDP stack.  Hence, I'm outlining 
below how to do the testing.  In the end of the message I include some 
practical advice.  This real life testing can be done by anyone who has a 
working Linux/Unix machine (Mac OS X is Unix), preferably with a working ARM 
compilation environment for the new runtime, an Ellduino board, an Ethernet 
cable, and the capability of powering the Ellduino board (either over USB, PoE, 
or separately).

In the very beginning, we will be using a fixed IP address of 10.0.0.2, a bad 
Ethernet address of 00:00:00:00:00:00, no ICMP, and no ARP.  Then we will add a 
real Ethernet address, ARP, ICMP, and (later on) DHCP.

Testing steps:

1. The MCU driver is able to receive Ethernet packets (seen via LEDs or serial 
line).
2. The Ethernet packets are passed to the IP layer.
3. The IP layer is able to verify the packet format and recognises its IP 
address.
4. The packets are passed to the CoAP layer.
5. The CoAP replies are received by the Ethernet layer.

CoAP-layer replying down to the Ethernet layer has already been tested with 
mockups.  However, even in this there may be bugs related to the differences 
between the emulator and the real hardware.  For example, I expect that the UDP 
socket and CoAP URL data structures may align differently, requiring #ifdefs.

6. The Ethernet layer passes the reply packets to the ENC28J60 chip.
7. The outgoing packets are visible in the Ethernet.
8. The outgoing packets are formatted correctly.
9. A CoAP transaction works correctly end-to-end.

The initial steps may be the most frustrating ones, as there it is very hard to 
figure out what is wrong (if anything).  I expect that most time will go to 
steps 8 and 9.  For Step 8 since the stack has been written from the ground up 
and is likely to contain subtle protocol bugs.  For Step 9 for the same reason 
plus because this is the first time I'm implementing CoAP.  (IP and related 
protocols I have implemented a few times in the past.)

----------------

The main tool for testing is either Wireshark or TCP dump.  I will most 
probably be using Wireshark myself.  https://www.wireshark.org

Now, before we can actually get to the testing, we need to prepare the laptop 
for working with an endpoint that doesn't implement ARP or DHCP.  For that we 
need to manually configure a static ARP table entry and configure the network 
interface to contain an IP address within the same subnet as the Ellduino board.

1. Configuring the host network interface

In Linux/Unix, each network interface is usually configured to have a single IP 
address, subnet mask, and default gateway.  However, it is possible to 
configure the interface to have multiple IP addresses.  The latter is often 
used for so-called multi-homing, in a situation where a single Ethernet network 
carries multiple IP subnetworks.  

In our case, whether the Ethernet carries just a single IP subnet or many 
depends on how the Ellduino and the testing Unix/Linux are connected to each 
other.  If they are connected with a direct cable (crossover or not, depending 
on the host Ethernet port), then one subnet is enough.  If they are connected 
through a switch or a hub, and the host uses the Ethernet also for other 
traffic, two subnets are needed.

In the examples below I expect that we are using two subnets and that the host 
is Unix.  In Linux the commands are different (and I don't remember them by 
heart), but the principles are the same.

In the host, the network interfaces are named with sort mnemonics, e.g. eth0, 
en2, bge3.  In both Linux and Unix you can list the interfaces with "ifconfig 
-a"; the output format is different.  In Mac OS X the output looks like the 
following:

   $ ifconfig -a
   lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 16384
        options=3<RXCSUM,TXCSUM>
        inet6 fe80::1%lo0 prefixlen 64 scopeid 0x1 
        inet 127.0.0.1 netmask 0xff000000 
        inet6 ::1 prefixlen 128 
   ...
   en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
        options=2b<RXCSUM,TXCSUM,VLAN_HWTAGGING,TSO4>
        media: autoselect (none)
        status: inactive
   ...

Here en0 is my laptop's Ethernet interface.  (I just need to know which 
mnemonic is which physical interface.  If I don't know, I need to connect the 
cable to a switch and figure out which interface changes its status from 
inactive to active.)  At this stage it is disconnected (status: inactive) and 
it does not have an IP address.  (I have removed the Ethernet address for 
privacy purposes.)

When I connect the Ethernet and wait for a while for my router to give my 
laptop a DHCP address, the output changes as follows:

   en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
        options=2b<RXCSUM,TXCSUM,VLAN_HWTAGGING,TSO4>
        inet6 fe80::cx2c:14zz:yy03:1111%en0 prefixlen 64 scopeid 0x5 
        inet 192.168.1.233 netmask 0xffffff00 broadcast 192.168.1.255
        media: autoselect (1000baseT <full-duplex,flow-control>)
        status: active

Now the interface is connected (status: active) and it has been assigned an IP 
address of 192.168.1.233, with the subnetwork mask (netmask) of 255.255.255.0 
(0xffffff00).  I can also see that it is connected to a gigabit switch and has 
been assigned an link-local IPv6 address (mangled).

Now, to prepare the interface for Ellduino IP/UDP testing, I need to configure 
the interface to be in the same subnetwork where the Ellduino fixed IP address 
is, i.e. in the 10.0.0.0/24 subnetwork.  In Unix, I can add an IP alias for 
that.  In Linux this is done differently and I don't remember how; we did 
configure the Raspberry Pi so in October with Teemu, but I don't remember the 
details.

Hence, in a Unix I give the following command:
  
   $ sudo ifconfig en0 10.0.0.1 netmask 255.255.255.0 alias

The "alias" in the end indicates that I want this address to be used in 
*addition* to the other IP address.  If I just wanted to replace the existing 
address, or configure an address when there is none, I could enter the same 
command without the "alias".

After that the output looks as follows:

  en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
        options=2b<RXCSUM,TXCSUM,VLAN_HWTAGGING,TSO4>
        inet6 fe80::cx2c:14zz:yy03:1111%en0 prefixlen 64 scopeid 0x5 
        inet 192.168.1.233 netmask 0xffffff00 broadcast 192.168.1.255
        inet 10.0.0.1 netmask 0xffffff00 broadcast 10.0.0.255
        media: autoselect (1000baseT <full-duplex,flow-control>)
        status: active

Here we can see that the interface now has two IP addresses, 192.168.1.233 in 
the subnet 192.168.1.233/24 and 10.0.0.1 in the subnet 10.0.0.0/24.

2. Adding a static ARP table entry

The ARP protocol maintains a table that lists the Ethernet address for each 
active remote IP address.  For example, in my laptop the arp table looks like 
the following at the moment (with the Ethernet addresses masked for privacy): 

   $ arp -a
   ? (192.168.1.17) at 0:x1:x2:x3:x4:x5 on en0 ifscope [ethernet]
   ? (192.168.1.18) at 0:y1:y2:y3:y4:y5 on en0 ifscope [ethernet]
   ? (192.168.2.4) at 0:z1:z2:z3:z4:z5 on en1 ifscope [ethernet]
   ? (192.168.2.17) at 0:s1:s2:s3:s4:s5 on en1 ifscope [ethernet]
   ? (192.168.2.164) at 0:t1:t2:t3:t4:t5 on en1 ifscope [ethernet]

Since the Ellduino board won't have ARP in the first phase, we need to manually 
create a static ARP entry for it.  The following command does this in Unix; 
again, the Linux syntax is likely to be somewhat different:

   $ sudo arp -s 10.0.0.2 00:00:00:00:00:00 ifscope en0

After that the ARP table looks like the following:

   $ arp -an
   ? (10.0.0.2) at 0:0:0:0:0:0 on en0 ifscope permanent [ethernet]
   ? (192.168.1.17) at 0:x1:x2:x3:x4:x5 on en0 ifscope [ethernet]
   ? (192.168.1.18) at 0:y1:y2:y3:y4:y5 on en0 ifscope [ethernet]
   ? (192.168.2.4) at 0:z1:z2:z3:z4:z5 on en1 ifscope [ethernet]
   ? (192.168.2.17) at 0:s1:s2:s3:s4:s5 on en1 ifscope [ethernet]
   ? (192.168.2.164) at 0:t1:t2:t3:t4:t5 on en1 ifscope [ethernet]

When you stop testing, it is a good idea to remove the entry

   $ sudo arp -d 10.0.0.2

-------------

With the host interface configured and the static ARP table entry there, it is 
now possible to start the actual testing.  

1. The MCU driver is able to receive Ethernet packets (seen via LEDs or serial 
line).

In the first step, we should just send packets to the board and see that they 
are received by the ENC28J60 chip receives them correctly and passes to the 
protocol stack.  For this initial testing ICMP is good enough:

  $ ping 10.0.0.2

At the same time, use wireshark (or tcpdump) to see that the packets actually 
go out from the host interface.  (It is best to use wireshark or tcpdump on a 
separate host, but that is more tricky with modern switches and not recommended 
for beginners.  If you have an old 10baseT hub that broadcasts every received 
packet on all ports, that would be perfect for this testing.)

Just as an example, while running the ping I can see the following in tcpdump 
(running on another window):

  $ sudo tcpdump -ni en0
  tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
  listening on en0, link-type EN10MB (Ethernet), capture size 65535 bytes
  07:52:19.225853 IP 10.0.0.1 > 10.0.0.2: ICMP echo request, id 3088, seq 0, 
length 64
  07:52:20.226951 IP 10.0.0.1 > 10.0.0.2: ICMP echo request, id 3088, seq 1, 
length 64
  07:52:21.228137 IP 10.0.0.1 > 10.0.0.2: ICMP echo request, id 3088, seq 2, 
length 64
  07:52:22.229215 IP 10.0.0.1 > 10.0.0.2: ICMP echo request, id 3088, seq 3, 
length 64

Once we know that the packets actually go out from the host, IIRC there is also 
a led in the Ellduino board that should blink when the ENC28J60 receives a 
packet.  

The next step is then to see that the packets are passed to the MCU.  For that 
the driver should be instrumented with a debug LED or serial line output.

2. The Ethernet packets are passed to the IP layer.
3. The IP layer is able to verify the packet format and recognises its IP 
address.
4. The packets are passed to the CoAP layer.
5. The CoAP replies are received by the Ethernet layer.
6. The Ethernet layer passes the reply packets to the ENC28J60 chip.

The following steps 2-6 are tested in the same way, just adding/moving the 
debug instrumentation into the next place in the stack source code.  In step 4 
it is no longer possible to use ICMP (unless someone implements a minimal ICMP 
that is able to respond to ICMP echo requests), but one has to move to UDP and 
then CoAP.  Copper is a CoAP implementation for the Firefox browser.  h5.coap 
is a CoAP implementation for node.js.  Both have been tested by Teemu and I and 
are known to work.

7. The outgoing packets are visible in the Ethernet.
8. The outgoing packets are formatted correctly.

In step 7 (or earlier if ICMP is there), the reply packets should start 
appearing on the network.  Hence, they should be visible in Wireshark or 
tcpdump.  Initially they are likely to be malformed, meaning that Wireshark or 
tcpdump may interpret them in a weird way.

9. A CoAP transaction works correctly end-to-end.

This step is ready when we'll be able to use real CoAP GETs and PUTs from 
Copper, node.js, or something similar.

--Pekka


Other related posts: