Happy is a tool created by Nest labs for lightweight orchestration of simulated network topologies. Happy is useful for the development and testing of IoT home area networks.
With Happy, you can:
In this Codelab, you'll learn how to get started with Happy, as well as the basics of Weave for IoT devices. The Weave implementation you'll use is OpenWeave, an open-source version released by Nest.
For this Codelab, we'll use Google Cloud Shell to run a Linux Docker container with Happy and Weave already built for you.
Click the I/O Codelabs extension in the top-right of a Chrome browser tab to obtain a temporary account:
These temporary accounts have existing projects that are set up with billing so that there are no costs associated for you with running this codelab.
Use these credentials to log into the machine or to open a new Google Cloud Console window https://console.cloud.google.com/. Accept the new account Terms of Service and any updates to Terms of Service.
Here's what you should see once logged in:
When presented with this console landing page, please select the only project available. Alternatively, from the console home page, click on Select a Project:
From the GCP Console click the Cloud Shell icon on the top right toolbar:
It should only take a few moments to provision and connect to the environment:
This virtual machine is loaded with all the development tools you'll need. It offers a persistent 5GB home directory, and runs on the Google Cloud, greatly enhancing network performance and authentication.
Once connected to Cloud Shell, click the Expand to new window button so that command output is easier to see:
Pull the happy-codelab
Docker container. It may take a few moments to start downloading.
$ gcloud docker --verbosity=error -- pull gcr.io/open-weave/happy-codelab:latest
Connect to the bash shell within the Docker container:
$ docker run --rm --privileged -ti gcr.io/open-weave/happy-codelab:latest bash
You now should have a prompt similar to this:
root@c0f3912a74ff:/#
Be aware that even though most commands in this Codelab are shown with a $
prompt, they are all entered at this Docker bash #
prompt.
Let's create the following three-node topology with Happy.
This topology is an example of a simple Home Area Network (HAN). In this HAN, two nodes are connected together in a Thread network, and one of those nodes connects to a third via Wi-Fi. This node can also be connected to a wireless router in the home to provide internet connectivity for the entire HAN. More about this later.
First, create the three nodes:
$ happy-node-add 01ThreadNode $ happy-node-add 02BorderRouter $ happy-node-add 03WiFiNode
Let's make sure they exist:
$ happy-node-list 01ThreadNode 02BorderRouter 03WiFiNode
Now let's create some networks:
$ happy-network-add ThreadNetwork thread $ happy-network-add WiFiNetwork wifi
Verify that the networks exist:
$ happy-network-list ThreadNetwork WiFiNetwork
Check the Happy state:
$ happy-state State Name: happy NETWORKS Name Type State Prefixes ThreadNetwork thread UP WiFiNetwork wifi UP NODES Name Interface Type IPs 01ThreadNode 02BorderRouter 03WiFiNode
It's not enough just to bring a network up—we have to add nodes to the networks. Following our topology diagram, add each node to the appropriate network(s):
$ happy-node-join 01ThreadNode ThreadNetwork $ happy-node-join 02BorderRouter ThreadNetwork $ happy-node-join 02BorderRouter WiFiNetwork $ happy-node-join 03WiFiNode WiFiNetwork
Note that 02BorderRouter
was added to both ThreadNetwork
and WiFiNetwork
. That's because as a Border Router within our HAN, this node connects the two individual networks together.
Check the Happy state. Each node's interfaces are up:
$ happy-state State Name: happy NETWORKS Name Type State Prefixes ThreadNetwork thread UP WiFiNetwork wifi UP NODES Name Interface Type IPs 01ThreadNode wpan0 thread 02BorderRouter wpan0 thread wlan0 wifi 03WiFiNode wlan0 wifi
Our topology now looks like this:
The last step in bringing up our Happy network is to assign IP addresses to each interface on each node. Specify the IP prefix for a network, and Happy automatically assigns IP addresses for you.
Because the Thread protocol uses IPv6, add an IPv6 prefix to the Thread network:
$ happy-network-address ThreadNetwork 2001:db8:1:2::
Check the Happy state. The Thread interfaces on each Thread node have IP addresses:
$ happy-state State Name: happy NETWORKS Name Type State Prefixes ThreadNetwork thread UP 2001:0db8:0001:0002/64 WiFiNetwork wifi UP NODES Name Interface Type IPs 01ThreadNode wpan0 thread 2001:0db8:0001:0002:3e36:13ff:fe33:732e/64 02BorderRouter wpan0 thread 2001:0db8:0001:0002:a651:3eff:fe92:6dbc/64 wlan0 wifi 03WiFiNode wlan0 wifi
For the WiFi network, add both IPv4 and IPv6 prefixes:
$ happy-network-address WiFiNetwork 2001:db8:a:b:: $ happy-network-address WiFiNetwork 10.0.1.0
Check the Happy state once more. All interfaces have assigned IP addresses, with two for each Wi-Fi interface:
$ happy-state State Name: happy NETWORKS Name Type State Prefixes ThreadNetwork thread UP 2001:0db8:0001:0002/64 WiFiNetwork wifi UP 2001:0db8:000a:000b/64 10.0.1/24 NODES Name Interface Type IPs 01ThreadNode wpan0 thread 2001:0db8:0001:0002:3e36:13ff:fe33:732e/64 02BorderRouter wpan0 thread 2001:0db8:0001:0002:a651:3eff:fe92:6dbc/64 wlan0 wifi 10.0.1.2/24 2001:0db8:000a:000b:426c:38ff:fe90:01e6/64 03WiFiNode wlan0 wifi 2001:0db8:000a:000b:9aae:2bff:fe71:62fa/64 10.0.1.3/24
Here is our updated topology:
Now that our Happy network is up and running, let's test its connectivity by pinging the other nodes from 01ThreadNode
:
$ happy-ping 01ThreadNode 02BorderRouter [Ping] ping from 01ThreadNode to 02BorderRouter on address 10.0.1.2 -> 100% packet loss [Ping] ping from 01ThreadNode to 02BorderRouter on address 2001:0db8:0001:0002:a651:3eff:fe92:6dbc -> 0% packet loss [Ping] ping from 01ThreadNode to 02BorderRouter on address 2001:0db8:000a:000b:426c:38ff:fe90:01e6 -> 100% packet loss $ happy-ping 01ThreadNode 03WiFiNode [Ping] ping from 01ThreadNode to 03WiFiNode on address 2001:0db8:000a:000b:9aae:2bff:fe71:62fa -> 100% packet loss [Ping] ping from 01ThreadNode to 03WiFiNode on address 10.0.1.3 -> 100% packet loss
The happy-ping
command tries to ping every IP address for every interface on the target node. We can ignore the IPv4 addresses because Thread only uses IPv6.
Note that only one IPv6 ping was successful: the one on 02BorderRouter
's wpan0
interface, which is the only address 01ThreadNode
can directly reach:
The other IPv6 addresses failed because forwarding has not been enabled between wpan0
and wlan0
on 02BorderRouter
. Thus, 01ThreadNode
has no idea 03WiFiNode
exists, or how to reach it. Happy has brought up the simulated network, but has not enabled all routing and forwarding between nodes.
To route IPv6 traffic across the HAN, add the proper routes to each node in each network, in both directions (so the ping knows how to return to the source node).
For each node, you'll need to know:
02BorderRouter
for bothFor our three node network, that gives us the following:
from Source Network | to Target Network | via Gateway |
|
|
|
|
|
|
This can be done individually for each node with happy-node-route
, but it's easier to do it for all nodes in each network with happy-network-route
.
$ happy-network-route -a -i ThreadNetwork -t default -v 02BorderRouter -p 2001:db8:1:2::/64 $ happy-network-route -a -i WiFiNetwork -t default -v 02BorderRouter -p 2001:db8:a:b::/64
For an explanation of the command-line flags, use happy-network-route -h
.
The happy-network-route
command also turns on IPv4 and IPv6 forwarding for each node, as needed. This allows traffic to route from one interface to another within a node.
Now retry the ping:
$ happy-ping 01ThreadNode 02BorderRouter [Ping] ping from 01ThreadNode to 02BorderRouter on address 10.0.1.2 -> 100% packet loss [Ping] ping from 01ThreadNode to 02BorderRouter on address 2001:0db8:0001:0002:a651:3eff:fe92:6dbc -> 0% packet loss [Ping] ping from 01ThreadNode to 02BorderRouter on address 2001:0db8:000a:000b:426c:38ff:fe90:01e6 -> 0% packet loss
Both IPv6 pings work! With forwarding on, it knows how to reach the wlan0
interface. The IPv4 ping still fails, because we only configured IPv6 routes and forwarding (also because Thread doesn't run over IPv4).
Since we added network routes to both sides, let's ping across networks:
$ happy-ping 01ThreadNode 03WiFiNode [Ping] ping from 01ThreadNode to 03WiFiNode on address 2001:0db8:000a:000b:9aae:2bff:fe71:62fa -> 0% packet loss [Ping] ping from 01ThreadNode to 03WiFiNode on address 10.0.1.3 -> 100% packet loss
The IPv6 ping works as expected. You now have a fully-functional, simulated IPv6 HAN.
To enable a more secure and reliable way of connecting everything together, let's add Weave on top of the HAN.
Weave is a network application layer that provides the secure and reliable communications backbone for Nest products. We can add Weave functionality with OpenWeave, the open-source version of Weave.
An implementation of Weave is called a "fabric". A Weave fabric is a network that comprises all HAN nodes, the Nest Service, and any mobile devices participating in the HAN. It sits on top of the HAN and enables easier routing across the different underlying network link technologies (for example, Thread or Wi-Fi).
Create the Weave fabric for your HAN, using fab1
as the Fabric ID, then configure all nodes for Weave:
$ weave-fabric-add fab1 $ weave-node-configure
Now that Weave is configured, check the Happy state:
$ happy-state State Name: happy NETWORKS Name Type State Prefixes ThreadNetwork thread UP 2001:0db8:0001:0002/64 WiFiNetwork wifi UP 2001:0db8:000a:000b/64 10.0.1/24 NODES Name Interface Type IPs 01ThreadNode wpan0 thread 2001:0db8:0001:0002:3e36:13ff:fe33:732e/64 fd00:0000:fab1:0006:6bca:9502:eb69:11e7/64 02BorderRouter wpan0 thread fd00:0000:fab1:0006:6a6a:f236:eb69:11e7/64 2001:0db8:0001:0002:a651:3eff:fe92:6dbc/64 wlan0 wifi fd00:0000:fab1:0001:6a6a:f236:eb69:11e7/64 10.0.1.2/24 2001:0db8:000a:000b:426c:38ff:fe90:01e6/64 03WiFiNode wlan0 wifi 2001:0db8:000a:000b:9aae:2bff:fe71:62fa/64 10.0.1.3/24 fd00:0000:fab1:0001:6b82:6e60:eb69:11e7/64
Each node has been added to the Weave fabric, and each interface has a new IPv6 address starting with fd00
. To get more information on the Weave fabric, use the weave-state
command:
$ weave-state State Name: weave NODES Name Weave Node Id Pairing Code 01ThreadNode 69ca9502eb6911e7 8ZJB5Q 02BorderRouter 686af236eb6911e7 B5YV3P 03WiFiNode 69826e60eb6911e7 L3VT3A FABRIC Fabric Id Global Prefix fab1 fd00:0000:fab1::/48
Here is our updated topology, with Weave values in blue:
There's a lot of new information in the Weave and Happy states. Let's start with the fabric from weave-state
:
FABRIC Fabric Id Global Prefix fab1 fd00:0000:fab1::/48
Weave uses an IPv6 prefix of fd00::/48
for each node. Addresses in this block are called Unique Local Addresses and are designated for use within private networks such as a HAN. Combining that with the Fabric ID generates the Weave Global Prefix shown above.
Each node in the Weave fabric is assigned a unique Node ID, along with a Pairing Code:
NODES Name Weave Node Id Pairing Code 01ThreadNode 69ca9502eb6911e7 8ZJB5Q 02BorderRouter 686af236eb6911e7 B5YV3P 03WiFiNode 69826e60eb6911e7 L3VT3A
The Node ID globally identifies a node in the Weave fabric. The Pairing Code is used as a "joiner credential" during the pairing process, and typically would be printed alongside a product's QR code.
For example, if you look at the QR code on a Nest Protect or a Nest Cam, you'll notice a 6-character string, often referred to as the Entry Key. This is the Weave Pairing Code.
Weave uses a combination of the Global Prefix, the Fabric ID, and the Node ID to create Weave-specific IPv6 addresses for each node and interface in the fabric.
Note that there are four new IPv6 addresses in the Happy topology, all starting with our Weave Global Prefix of fd00:0000:fab1::/48
.
NODES Name Interface Type IPs 01ThreadNode wpan0 thread 2001:0db8:0001:0002:3e36:13ff:fe33:732e/64 fd00:0000:fab1:0006:6bca:9502:eb69:11e7/64 02BorderRouter wpan0 thread fd00:0000:fab1:0006:6a6a:f236:eb69:11e7/64 2001:0db8:0001:0002:a651:3eff:fe92:6dbc/64 wlan0 wifi fd00:0000:fab1:0001:6a6a:f236:eb69:11e7/64 10.0.1.2/24 2001:0db8:000a:000b:426c:38ff:fe90:01e6/64 03WiFiNode wlan0 wifi 2001:0db8:000a:000b:9aae:2bff:fe71:62fa/64 10.0.1.3/24 fd00:0000:fab1:0001:6b82:6e60:eb69:11e7/64
Weave protocols use these addresses to communicate across the Weave fabric, rather than the standard IPv6 addresses assigned to each node.
Weave nodes on a Thread network need to know where to exit that network. A Weave network gateway—typically on a Thread Border Router—provides this functionality.
In our sample topology, let's designate the BorderRouter node as the Weave network gateway:
$ weave-network-gateway ThreadNetwork 02BorderRouter
This command adds a route from all Thread nodes to the Weave fabric subnet (fd:0:fab1::/48
) via the BorderRouter
node's Thread interface (wpan0
), which enables each Thread node to reach any Weave node beyond the Thread network. This is analogous to the happy-network-route
command we used earlier, but specific to Weave fabric routes.
What makes Happy so powerful is how it easily manages all setup and teardown of a simulated topology.
Save your Happy topology for later use:
$ happy-state -s codelab.json
This places a JSON file with the complete topology in your root ~/
folder. The JSON file is a copy of your current Happy state, which is found at ~/.happy_state.json
.
Once saved, delete the current topology:
$ happy-state-delete
This deletes all network namespaces and related configurations found in the ~/.happy-state.json
file. Check happy-state
and weave-state
to confirm an empty configuration:
$ happy-state State Name: happy NETWORKS Name Type State Prefixes NODES Name Interface Type IPs $ weave-state State Name: weave NODES Name Weave Node Id Pairing Code FABRIC Fabric Id Global Prefix
To reload a saved configuration, use one of two commands:
happy-state-load
— does not support Weave pluginweave-state-load
— supports Weave pluginSo if your topology includes Weave, always use the weave-state-load
command so that the Weave fabric and associated configuration is applied.
Reload the saved Happy topology:
$ weave-state-load codelab.json
Check all states to confirm a successful loading:
$ happy-state State Name: happy NETWORKS Name Type State Prefixes ThreadNetwork thread UP 2001:0db8:0001:0002/64 WiFiNetwork wifi UP 2001:0db8:000a:000b/64 10.0.1/24 NODES Name Interface Type IPs 01ThreadNode wpan0 thread 2001:0db8:0001:0002:eef6:a0ff:feca:6697/64 fd00:0000:fab1:0006:6bca:9502:eb69:11e7/64 02BorderRouter wpan0 thread fd00:0000:fab1:0006:6a6a:f236:eb69:11e7/64 2001:0db8:0001:0002:5e53:bbff:fe05:484b/64 wlan0 wifi 2001:0db8:000a:000b:2e61:fdff:fed9:4fbc/64 fd00:0000:fab1:0001:6a6a:f236:eb69:11e7/64 10.0.1.2/24 03WiFiNode wlan0 wifi fd00:0000:fab1:0001:6b82:6e60:eb69:11e7/64 10.0.1.3/24 2001:0db8:000a:000b:5e8e:c9ff:fed2:bdd1/64 $ weave-state State Name: weave NODES Name Weave Node Id Pairing Code 01ThreadNode 69ca9502eb6911e7 8ZJB5Q 02BorderRouter 686af236eb6911e7 B5YV3P 03WiFiNode 69826e60eb6911e7 L3VT3A FABRIC Fabric Id Global Prefix fab1 fd00:0000:fab1::/48
Happy uses Linux network namespaces to simulate complex topologies. Typically, a network configuration applies across the entire Linux OS. Network namespaces allow you to partition network configurations so that each namespace has its own set of interfaces and routing tables.
Each node and network in Happy is a network namespace, while links between them are network interfaces.
For example, using our topology:
Let's see what namespaces Happy created for this:
$ ip netns list happy004 happy003 happy002 happy001 happy000
If you check the netns
section of the Happy state JSON file, you can see what nodes and networks each namespace corresponds to:
$ happy-state -j | grep "netns" -A 5 "netns": { "01ThreadNode": "000", "02BorderRouter": "001", "03WiFiNode": "002", "ThreadNetwork": "003", "WiFiNetwork": "004",
Commands issued to nodes are basic terminal commands executed from within each node's namespace. An easy way to see this is to enable Happy run-time logs.
To view logs, you have to log into the existing Docker container. In the main Cloud Shell terminal, get the Container ID from the bash prompt.
For example, if your prompt looks like this:
root@c0f3912a74ff:/#
The Docker Container ID is the value after the @ sign: c0f3912a74ff
Copy this value.
Select + to open a second Cloud Shell terminal window:
Login to the existing Docker container using the Container ID you copied:
$ docker exec -it <container-id> bash
For example:
$ docker exec -it c0f3912a74ff bash
The terminal prompt should be the same as the original Cloud Shell one:
root@c0f3912a74ff:/#
Run happy-state
and weave-state
to confirm. If connected to the same Docker container, these commands output the topology you've been working on:
$ happy-state State Name: happy NETWORKS Name Type State Prefixes ThreadNetwork thread UP 2001:0db8:0001:0002/64 WiFiNetwork wifi UP 2001:0db8:000a:000b/64 10.0.1/24 NODES Name Interface Type IPs 01ThreadNode wpan0 thread 2001:0db8:0001:0002:eef6:a0ff:feca:6697/64 fd00:0000:fab1:0006:6bca:9502:eb69:11e7/64 02BorderRouter wpan0 thread fd00:0000:fab1:0006:6a6a:f236:eb69:11e7/64 2001:0db8:0001:0002:5e53:bbff:fe05:484b/64 wlan0 wifi 2001:0db8:000a:000b:2e61:fdff:fed9:4fbc/64 fd00:0000:fab1:0001:6a6a:f236:eb69:11e7/64 10.0.1.2/24 03WiFiNode wlan0 wifi fd00:0000:fab1:0001:6b82:6e60:eb69:11e7/64 10.0.1.3/24 2001:0db8:000a:000b:5e8e:c9ff:fed2:bdd1/64 $ weave-state State Name: weave NODES Name Weave Node Id Pairing Code 01ThreadNode 69ca9502eb6911e7 8ZJB5Q 02BorderRouter 686af236eb6911e7 B5YV3P 03WiFiNode 69826e60eb6911e7 L3VT3A FABRIC Fabric Id Global Prefix fab1 fd00:0000:fab1::/48
Now, turn on Happy logs, they will run continuously on this second terminal window:
$ happy-state -l
Go back to the first window and run a Happy ping:
$ happy-ping 01ThreadNode 02BorderRouter
Check the most recent log entries in the second terminal window. You should see a line like this in the logs:
DEBUG [Driver:CallCmd():416] Happy [happy]: > ip netns exec happy000 ping6 -c 1 2001:0db8:0001:0002:5e53:bbff:fe05:484b
The happy-ping
command is nothing more than Happy running the ping6
command in the happy000
namespace (01ThreadNode
).
Use happy-shell
to run non-Happy commands as if logged into one of the nodes (network namespaces):
$ happy-shell 01ThreadNode root@01ThreadNode:#
Simulated devices are run within each namespace, and they only have access to the network configuration specified through Happy.
Check the interface configuration for the node. This will be different than your OS-wide configuration and should reflect what's listed in the Happy state:
root@01ThreadNode:# ifconfig lo Link encap:Local Loopback inet addr:127.0.0.1 Mask:255.0.0.0 inet6 addr: ::1/128 Scope:Host UP LOOPBACK RUNNING MTU:65536 Metric:1 RX packets:1 errors:0 dropped:0 overruns:0 frame:0 TX packets:1 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1 RX bytes:152 (152.0 B) TX bytes:152 (152.0 B) wpan0 Link encap:Ethernet HWaddr ec:f6:a0:ca:66:97 inet6 addr: fd00:0:fab1:6:6bca:9502:eb69:11e7/64 Scope:Global inet6 addr: 2001:db8:1:2:eef6:a0ff:feca:6697/64 Scope:Global inet6 addr: fe80::eef6:a0ff:feca:6697/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:32 errors:0 dropped:0 overruns:0 frame:0 TX packets:26 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:2832 (2.8 KB) TX bytes:2348 (2.3 KB)
Use exit
to leave the node's namespace:
root@01ThreadNode:# exit
You now know:
Check openweave.io for a variety of references!