Files
mongoose/test/mesh/README.md
T

8.1 KiB

Ethernet over a radio mesh

The idea

Small radio devices, e.g. IEEE 802.15.4, sub-GHz or LoRa boards, should run Mongoose as if they were on an ordinary Ethernet LAN: get an IP address over DHCP, answer HTTP, talk MQTT. No 6LoWPAN, no Thread, no special stack.

To do that, the radio interface looks like Ethernet to Mongoose (MG_TCPIP_L2_ETH). Its driver splits each Ethernet frame into radio frames, sends them over the air, and the receiver puts them back together. One or more bridges connect the radio network to the wired LAN: a bridge has an Ethernet port and a radio. A node in range of a bridge talks to it directly; a node out of range reaches a bridge through other nodes, which forward its frames. That makes the radio network a mesh. With several bridges, each node uses one of them, e.g. the nearest; the mesh covers a bigger area, and keeps working if a bridge goes down. A PC on the LAN sees the radio nodes as regular hosts on the same LAN.

             +---- bridge 1 ~~~ node ~~~ node
             |                    ~
  PC ---- LAN                    node
             |                    ~
             +---- bridge 2 ~~~ node ~~~ node ~~~ node

~~~ and ~ are radio hops. Nodes far from a bridge send their frames through nearer nodes. A node in the middle can reach either bridge.

Two parts plug in

  • struct mg_radio, a radio standard: how big a radio frame is, how addresses are written into it. mg_radio_802154 is the first one.
  • struct mg_mesh, a mesh strategy: how nodes find a bridge, and where each frame goes next. mg_mesh_star is the first and simplest one: every node talks to a bridge directly, one hop.

Everything else, splitting and reassembling frames, sending again when a frame is lost, is common code that works the same for every radio and every mesh. The goal is to swap radios and mesh strategies without touching anything else.

Why a simulator

Real boards are slow to test with, and you can't put 500 of them on a desk. The simulator runs a whole network in one process: every node is a real Mongoose instance, only the radio and the air between nodes are emulated. That lets us try mesh strategies with many nodes, and measure how long it takes nodes to join, get an address, and answer a request.

Virtual time

The simulator has its own clock, simulator::now, in microseconds, and Mongoose reads it through mg_millis(). Nothing waits for real time: the simulator moves the clock forward itself, 250 us per step, and polls every station once per step. A minute of network traffic takes about a second to simulate. -t SEC sets how long to run, 10 minutes by default.

The step is a trade-off. Each step polls every station's Mongoose, and even an idle poll costs something, so a smaller step means a slower run. A bigger step makes frames wait longer in queues, see below. Measured with 121 nodes and 10 simulated minutes:

Step Request time, mean Run time
1 ms 6.1 ms 0.9 s
250 us 1.5 ms 3.0 s
100 us 0.6 ms 7.2 s
10 us 0.06 ms 72 s

The setup

  DHCP server ---------+
   10.0.0.1            |
   02:01:00:00:00:00   +-- LAN ---- bridge ~~~ nodes
                       |
  test station --------+
   10.0.0.2
   02:02:00:00:00:00

The LAN is assumed infinitely fast, see "How frames move on the LAN" below. Two stations are on it:

  • The DHCP server, 10.0.0.1, MAC 02:01:00:00:00:00, gives each node an address made from its MAC: 02:00:00:X:Y:Z gets 10.X+1.Y.Z, so node addresses never clash with the LAN stations' 10.0.0.x.
  • The test station, 10.0.0.2, MAC 02:02:00:00:00:00, is a Mongoose instance. It waits for the mesh to settle, 60 seconds by default (-w SEC), then sends GET / to each node in turn, one at a time, at the node's own IP address. A node with no address, or one that doesn't answer within 10 seconds, counts as failed. At the end it prints how many requests succeeded and how long they took.

Bridges are on the LAN too, and get every frame, as if in promiscuous mode: a frame for one of their radio nodes isn't addressed to the bridge itself. A bridge forwards every frame it gets from the LAN to the radio, and every frame from the radio to the LAN, with no filtering yet.

Bridges and radio nodes get MACs 02:00:00:xx:xx:xx, numbered from 1 in command-line order: 02:00:00:00:00:01, 02:00:00:00:00:02, and so on.

How frames move on the LAN

Every LAN station, the DHCP server, the test station and each bridge, has a LAN interface, lif: a struct mg_tcpip_if with a receive queue. All of them use the same driver, s_lan_driver. There are no cables and no switch:

  • Sending (tx). A station sends a frame with its driver's tx, which is lan_tx(). It looks at the destination MAC and copies the frame into the receive queues of the stations it is for:

    • a broadcast or multicast goes to every station but the sender;
    • a unicast goes to the station with that MAC;
    • bridges get every frame, as a real bridge in promiscuous mode would: a frame for a radio node is addressed to the node, not to its bridge.

    That's all tx does. Nobody's code runs on the receiving side yet.

  • Receiving (rx). Each station takes the frames from its own queue when the main loop polls it:

    • the test station runs Mongoose, which reads the queue by itself, as it would with a real Ethernet driver that queues frames from an interrupt;
    • the DHCP server reads it in dhcp_poll(), and replies by sending through its own lif;
    • bridges read it in bridges_poll(), and forward it to the radio.

So a frame waits in a queue until its receiver's next poll, at most one step of virtual time, 250 us. Ethernet is hundreds of times faster than the radio, so that doesn't matter for what we measure. And since sending never calls the receiver's code, a reply is never sent from inside another station's send.

For example, a node asking for an address:

  1. The node's DHCP DISCOVER crosses the radio to a bridge, which sends it out of its lif. It is a broadcast, so lan_tx() puts it into the queues of the DHCP server, the test station and the other bridges.
  2. On its next poll, the DHCP server reads it and sends an OFFER, also a broadcast, which lands in the test station's and the bridges' queues.
  3. On their next poll, the bridges read the OFFER and send it over the radio to the node. The test station's Mongoose drops it: the test station has a fixed address and no DHCP client.

Files

File What
l2_mesh.h, l2_mesh.c struct mg_radio, struct mg_mesh, common code
l2_802154.h, l2_802154.c mg_radio_802154, IEEE 802.15.4
star.h, star.c mg_mesh_star, the star mesh
simulator.h, simulator.c The simulator
Makefile Builds and runs the simulator

Where we are

It works end to end: nodes get addresses over DHCP, and the test station's requests reach them through the bridges. But the radio is ideal for now:

  • The radio driver works like the LAN's: it puts whole Ethernet frames straight into receive queues, all of them for a broadcast, else the one with the destination MAC, and bridges get every frame. No range, no loss, no airtime.
  • Bridges forward everything, with no filtering.
  • So use one bridge for now. With two, a broadcast from the LAN goes to the radio through both, each hears the other's copy and sends it back to the LAN, and that never ends. Deciding who forwards what, without loops, is the job of mg_mesh.

mg_radio and mg_mesh are still stubs. Next steps, one at a time: the air (range, loss, airtime), fragmenting frames into radio frames with mg_radio, choosing a bridge and forwarding through nodes with mg_mesh, and filtering at the bridges.

Running

make                          # 100x100 m, a bridge, 11x11 nodes
make X=200 Y=50 ROWS=2 COLS=8 # Another area and grid

make puts a bridge in the middle of the area, and a node at every corner of a ROWS x COLS grid over it. The simulator itself takes:

./mesh_sim -area X,Y,Z -bridge X,Y,Z -node X,Y,Z ... -t SEC -w SEC

Positions are in metres; X,Y means Z is 0. Repeat -bridge and -node for more.