sniffing 802.15.4 packets natively with Raspberry Pi and Wireshark

August 25, 2014

It turns out that Linux's 802.15.4 implementation is just about ready to facilitate promiscuous packet sniffing natively. With minimal patching, we can now use a Raspberry Pi and 802.15.4 radio to sniff raw 802.15.4 packets using Wireshark or tcpdump or whatever.

Here I'll demonstrate the method I've come to prefer: Sniffing packets on the RPi with tcpdump and sending them over wifi using netcat to Wireshark running on another (less graphically laggy) computer.

Wireshark will work with an 802.15.4 interface out of the box of course if you already have a recent Linux kernel (>=3.15 maybe) with 6LoWPAN and 802.15.4 support (and a recent enough version of Wireshark I guess). Without promiscuous mode though, the kernel will drop any packets that are addressed to other hosts or that aren't valid 802.15.4 packets. With the patches below, Linux will pass every packet that the 802.15.4 interface receives straight to Wireshark whether it's a valid 802.15.4 packet or 6LoWPAN or Zigbee packet or none of the above.

Promiscuous mode isn't implemented yet at the 802.15.4 MAC layer, but it needs only minimal patching. The same goes for the driver for the AT86RF233 module I'm using. The patches are below, but first I have to mention some changes in Linux 802.15.4 development since my last entry on the subject.

kernel development

Previously, development had been occurring on David Miller's net-next branch. Alexander Aring now maintains linux-wpan and linux-wpan-next branches based on bluetooth and bluetooth-next, as well as a fork of the userspace tools here. Fortunately this change between branches does not seem to have caused any problems with running on the RPi.

There is also a new mailing list linux-wpan to replace the poorly named (and now obsolete?) linux-zigbee list.

patching the kernel

So then, on with the patches. These apply currently to linux-wpan-next. So, assuming you've grabbed that branch more or less like this:

git clone https://github.com/linux-wpan/linux-wpan-next.git

Then you can apply the following four patches in succession like so:

git am -

That's git am folowed by a space and a dash. The dash tells git to read the patch from the standard input instead of a file. That way you can just paste each patch file into the console from the clipboard. After pasting each file, push Ctrl-D to finish and apply the patch.


From 06fd29bc4b9d6d8eadc2b7005fcaca57730c4346 Mon Sep 17 00:00:00 2001
From: Thomas Stilwell <stilwelltNOSPAM@openlabs.co>
Date: Mon, 25 Aug 2014 23:35:22 -0500
Subject: [PATCH 1/4] ieee802154: skip bad frame type warning for ack type

otherwise there are many warnings with promiscuity on
---
 net/mac802154/wpan.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/net/mac802154/wpan.c b/net/mac802154/wpan.c
index fbe9252..47f7a60 100644
--- a/net/mac802154/wpan.c
+++ b/net/mac802154/wpan.c
@@ -484,6 +484,7 @@ mac802154_subif_frame(struct mac802154_sub_if_data *sdata, struct sk_buff *skb,
 	default:
 		pr_warn("ieee802154: bad frame received (type = %d)\n",
 			mac_cb(skb)->type);
+	case IEEE802154_FC_TYPE_ACK:
 		goto fail;
 	}
 
-- 
1.9.2



From 07f6a68282f560fb5aaffdd66ab2d38b372b3af1 Mon Sep 17 00:00:00 2001
From: Thomas Stilwell <stilwelltNOSPAM@openlabs.co>
Date: Tue, 26 Aug 2014 00:05:49 -0500
Subject: [PATCH 2/4] ieee802154: at86rf230: add support for promiscuous mode

---
 drivers/net/ieee802154/at86rf230.c | 25 +++++++++++++++++++++++--
 include/net/mac802154.h            |  1 +
 2 files changed, 24 insertions(+), 2 deletions(-)

diff --git a/drivers/net/ieee802154/at86rf230.c b/drivers/net/ieee802154/at86rf230.c
index 1bafab3..20a2a09 100644
--- a/drivers/net/ieee802154/at86rf230.c
+++ b/drivers/net/ieee802154/at86rf230.c
@@ -90,6 +90,7 @@ struct at86rf230_local {
 
 	bool tx_aret;
 	bool is_tx;
+	int rx_flags;
 	/* spinlock for is_tx protection */
 	spinlock_t lock;
 	struct completion tx_complete;
@@ -940,15 +941,16 @@ at86rf230_write_frame(void *context)
 	struct sk_buff *skb = lp->tx_skb;
 	u8 *buf = lp->tx.buf;
 	int rc;
+	int length = lp->dev->flags & IEEE802154_HW_OMIT_CKSUM ? skb->len + 2 : skb->len;
 
 	spin_lock(&lp->lock);
 	lp->is_tx = 1;
 	spin_unlock(&lp->lock);
 
 	buf[0] = CMD_FB | CMD_WRITE;
-	buf[1] = skb->len + 2;
+	buf[1] = length;
 	memcpy(buf + 2, skb->data, skb->len);
-	lp->tx.trx.len = skb->len + 2;
+	lp->tx.trx.len = length;
 	lp->tx.msg.complete = at86rf230_write_frame_complete;
 	rc = spi_async(lp->spi, &lp->tx.msg);
 	if (rc)
@@ -1235,6 +1237,24 @@ at86rf230_set_frame_retries(struct ieee802154_dev *dev, s8 retries)
 	return rc;
 }
 
+static void
+at86rf230_set_rx_flags(struct ieee802154_dev *dev, int flags)
+{
+	struct at86rf230_local *lp = dev->priv;
+	lp->rx_flags ^= flags;
+
+	if(lp->rx_flags & IFF_PROMISC)
+	{
+		lp->dev->flags &= ~IEEE802154_HW_OMIT_CKSUM;
+		at86rf230_write_subreg(lp, SR_AACK_PROM_MODE, 1);
+	}
+	else
+	{
+		lp->dev->flags |= IEEE802154_HW_OMIT_CKSUM;
+		at86rf230_write_subreg(lp, SR_AACK_PROM_MODE, 0);
+	}
+}
+
 static struct ieee802154_ops at86rf230_ops = {
 	.owner = THIS_MODULE,
 	.xmit = at86rf230_xmit,
@@ -1249,6 +1269,7 @@ static struct ieee802154_ops at86rf230_ops = {
 	.set_cca_ed_level = at86rf230_set_cca_ed_level,
 	.set_csma_params = at86rf230_set_csma_params,
 	.set_frame_retries = at86rf230_set_frame_retries,
+	.set_rx_flags = at86rf230_set_rx_flags,
 };
 
 static struct at86rf2xx_chip_data at86rf233_data = {
diff --git a/include/net/mac802154.h b/include/net/mac802154.h
index 2e67cdd..dce384c 100644
--- a/include/net/mac802154.h
+++ b/include/net/mac802154.h
@@ -183,6 +183,7 @@ struct ieee802154_ops {
 					   u8 min_be, u8 max_be, u8 retries);
 	int		(*set_frame_retries)(struct ieee802154_dev *dev,
 					     s8 retries);
+	void		(*set_rx_flags)(struct ieee802154_dev *dev, int flags);
 };
 
 /* Basic interface to register ieee802154 device */
-- 
1.9.2



From b072629419e282f777324e7dfd6d4622283d0399 Mon Sep 17 00:00:00 2001
From: Thomas Stilwell <stilwelltNOSPAM@openlabs.co>
Date: Tue, 26 Aug 2014 00:10:14 -0500
Subject: [PATCH 3/4] ieee802154: add support for promiscuous mode

---
 include/net/wpan-phy.h         |  1 +
 net/mac802154/ieee802154_dev.c | 10 ++++++++++
 net/mac802154/monitor.c        | 10 ++++++++++
 net/mac802154/wpan.c           | 11 +++++++++++
 4 files changed, 32 insertions(+)

diff --git a/include/net/wpan-phy.h b/include/net/wpan-phy.h
index 10ab0fc..4ad4f57 100644
--- a/include/net/wpan-phy.h
+++ b/include/net/wpan-phy.h
@@ -68,6 +68,7 @@ struct wpan_phy {
 	int (*set_csma_params)(struct wpan_phy *phy, u8 min_be, u8 max_be,
 			       u8 retries);
 	int (*set_frame_retries)(struct wpan_phy *phy, s8 retries);
+	void (*set_rx_flags)(struct wpan_phy *phy, int flags);
 
 	char priv[0] __attribute__((__aligned__(NETDEV_ALIGN)));
 };
diff --git a/net/mac802154/ieee802154_dev.c b/net/mac802154/ieee802154_dev.c
index b36b2b9..9e4db64 100644
--- a/net/mac802154/ieee802154_dev.c
+++ b/net/mac802154/ieee802154_dev.c
@@ -239,6 +239,13 @@ static int mac802154_set_frame_retries(struct wpan_phy *phy, s8 retries)
 	return priv->ops->set_frame_retries(&priv->hw, retries);
 }
 
+void mac802154_set_rx_flags(struct wpan_phy *phy, int flags)
+{
+	struct mac802154_priv *priv = wpan_phy_priv(phy);
+
+	priv->ops->set_rx_flags(&priv->hw, flags);
+}
+
 struct ieee802154_dev *
 ieee802154_alloc_device(size_t priv_data_len, struct ieee802154_ops *ops)
 {
@@ -362,6 +369,9 @@ int ieee802154_register_device(struct ieee802154_dev *dev)
 	priv->phy->add_iface = mac802154_add_iface;
 	priv->phy->del_iface = mac802154_del_iface;
 
+	if (priv->ops->set_rx_flags)
+		priv->phy->set_rx_flags = mac802154_set_rx_flags;
+
 	rc = wpan_phy_register(priv->phy);
 	if (rc < 0)
 		goto out_wq;
diff --git a/net/mac802154/monitor.c b/net/mac802154/monitor.c
index a68230e..1ecdf8a 100644
--- a/net/mac802154/monitor.c
+++ b/net/mac802154/monitor.c
@@ -86,10 +86,20 @@ void mac802154_monitors_rx(struct mac802154_priv *priv, struct sk_buff *skb)
 	rcu_read_unlock();
 }
 
+void mac802154_monitor_set_rx_flags(struct net_device *dev, int flags)
+{
+	struct mac802154_sub_if_data *priv = netdev_priv(dev);
+	struct wpan_phy *phy = priv->hw->phy;
+
+	if(phy->set_rx_flags)
+		return phy->set_rx_flags(phy, flags);
+}
+
 static const struct net_device_ops mac802154_monitor_ops = {
 	.ndo_open		= mac802154_slave_open,
 	.ndo_stop		= mac802154_slave_close,
 	.ndo_start_xmit		= mac802154_monitor_xmit,
+	.ndo_change_rx_flags	= mac802154_monitor_set_rx_flags,
 };
 
 void mac802154_monitor_setup(struct net_device *dev)
diff --git a/net/mac802154/wpan.c b/net/mac802154/wpan.c
index 47f7a60..0288377 100644
--- a/net/mac802154/wpan.c
+++ b/net/mac802154/wpan.c
@@ -146,6 +146,16 @@ void mac802154_get_mac_params(struct net_device *dev,
 	mutex_unlock(&priv->hw->slaves_mtx);
 }
 
+void mac802154_wpan_set_rx_flags(struct net_device *dev, int flags)
+{
+	struct mac802154_sub_if_data *priv = netdev_priv(dev);
+	struct wpan_phy *phy = priv->hw->phy;
+
+	if(phy->set_rx_flags)
+		return phy->set_rx_flags(phy, flags);
+	return;
+}
+
 static int mac802154_wpan_open(struct net_device *dev)
 {
 	int rc;
@@ -357,6 +367,7 @@ static const struct net_device_ops mac802154_wpan_ops = {
 	.ndo_start_xmit		= mac802154_wpan_xmit,
 	.ndo_do_ioctl		= mac802154_wpan_ioctl,
 	.ndo_set_mac_address	= mac802154_wpan_mac_addr,
+	.ndo_change_rx_flags	= mac802154_wpan_set_rx_flags,
 };
 
 static void mac802154_wpan_free(struct net_device *dev)
-- 
1.9.2



From 09d0c86495c864fa5e1d2a3206ab778cfe216c69 Mon Sep 17 00:00:00 2001
From: Thomas Stilwell <stilwelltNOSPAM@openlabs.co>
Date: Tue, 26 Aug 2014 00:11:09 -0500
Subject: [PATCH 4/4] ieee802154: at86rf230: hack to make fcs show properly in
 wireshark

---
 drivers/net/ieee802154/at86rf230.c | 3 ---
 net/mac802154/monitor.c            | 7 +------
 net/mac802154/rx.c                 | 6 ++++++
 net/mac802154/tx.c                 | 4 ++--
 4 files changed, 9 insertions(+), 11 deletions(-)

diff --git a/drivers/net/ieee802154/at86rf230.c b/drivers/net/ieee802154/at86rf230.c
index 20a2a09..e074414 100644
--- a/drivers/net/ieee802154/at86rf230.c
+++ b/drivers/net/ieee802154/at86rf230.c
@@ -803,9 +803,6 @@ at86rf230_rx(struct at86rf230_local *lp,
 
 	memcpy(skb_put(skb, len), rx_local_buf, len);
 
-	/* We do not put CRC into the frame */
-	skb_trim(skb, len - 2);
-
 	ieee802154_rx_irqsafe(lp->dev, skb, lqi);
 }
 
diff --git a/net/mac802154/monitor.c b/net/mac802154/monitor.c
index 1ecdf8a..1902546 100644
--- a/net/mac802154/monitor.c
+++ b/net/mac802154/monitor.c
@@ -65,8 +65,6 @@ void mac802154_monitors_rx(struct mac802154_priv *priv, struct sk_buff *skb)
 {
 	struct sk_buff *skb2;
 	struct mac802154_sub_if_data *sdata;
-	u16 crc = crc_ccitt(0, skb->data, skb->len);
-	u8 *data;
 
 	rcu_read_lock();
 	list_for_each_entry_rcu(sdata, &priv->slaves, list) {
@@ -76,10 +74,7 @@ void mac802154_monitors_rx(struct mac802154_priv *priv, struct sk_buff *skb)
 
 		skb2 = skb_clone(skb, GFP_ATOMIC);
 		skb2->dev = sdata->dev;
-		skb2->pkt_type = PACKET_HOST;
-		data = skb_put(skb2, 2);
-		data[0] = crc & 0xff;
-		data[1] = crc >> 8;
+		skb2->protocol = htons(ETH_P_IEEE802154);
 
 		netif_rx_ni(skb2);
 	}
diff --git a/net/mac802154/rx.c b/net/mac802154/rx.c
index fcb52bf..9ed5804 100644
--- a/net/mac802154/rx.c
+++ b/net/mac802154/rx.c
@@ -62,6 +62,8 @@ mac802154_subif_rx(struct ieee802154_dev *hw, struct sk_buff *skb, u8 lqi)
 	if (!(priv->hw.flags & IEEE802154_HW_OMIT_CKSUM)) {
 		u16 crc;
 
+		mac802154_monitors_rx(priv, skb);
+
 		if (skb->len < 2) {
 			pr_debug("got invalid frame\n");
 			goto fail;
@@ -72,9 +74,13 @@ mac802154_subif_rx(struct ieee802154_dev *hw, struct sk_buff *skb, u8 lqi)
 			goto fail;
 		}
 		skb_trim(skb, skb->len - 2); /* CRC */
+		mac802154_wpans_rx(priv, skb);
+
+		return;
 	}
 
 	mac802154_monitors_rx(priv, skb);
+	skb_trim(skb, skb->len - 2); /* CRC */
 	mac802154_wpans_rx(priv, skb);
 
 	return;
diff --git a/net/mac802154/tx.c b/net/mac802154/tx.c
index fdf4c0e6..57edfa3 100644
--- a/net/mac802154/tx.c
+++ b/net/mac802154/tx.c
@@ -92,8 +92,6 @@ netdev_tx_t mac802154_tx(struct mac802154_priv *priv, struct sk_buff *skb,
 		goto err_tx;
 	}
 
-	mac802154_monitors_rx(mac802154_to_priv(&priv->hw), skb);
-
 	if (!(priv->hw.flags & IEEE802154_HW_OMIT_CKSUM)) {
 		u16 crc = crc_ccitt(0, skb->data, skb->len);
 		u8 *data = skb_put(skb, 2);
@@ -102,6 +100,8 @@ netdev_tx_t mac802154_tx(struct mac802154_priv *priv, struct sk_buff *skb,
 		data[1] = crc >> 8;
 	}
 
+	mac802154_monitors_rx(priv, skb);
+
 	if (skb_cow_head(skb, priv->hw.extra_tx_headroom))
 		goto err_tx;
 
-- 
1.9.2


With these patches in place, promiscuous mode just works, but we also want to take advantage of using the 802.15.4 monitor interface so we get the packets complete with original FCS (checksum) in our dumps, as well as packets that aren't valid 802.15.4. We just need to activate a mon0 interface like so:

iz monitor wpan-phy0 mon0

This is the interface we'll use for capturing now.

tcpdump

At this point we can simply run tcpdump on the RPi like so and promiscuous mode will be activated automatically:

tcpdump -i mon0

wireshark

tcpdump is handy enough, but what's really nice is using Wireshark. We can do this two ways. We can run Wireshark on the RPi which is slow or we can run Wireshark on something else which is not slow.

Most straightforward is to run Wireshark on the RPi, perhaps over the network with X11 forwarding:

ssh -X root@alarmpi
wireshark -ki mon0

This works but the interface might not be particularly responsive with Wireshark running on the RPi itself. An alternative is to use tcpdump to forward the packets from the RPi to some other computer running Wireshark using netcat. This is my favorite.

On the "other computer", run:

nc -vlp 9000 | wireshark -ki -

And on the RPi:

tcpdump -i mon0 -Uw - | nc 192.168.1.188 9000

The result looks like this:

Sometimes I see a bit of Zigbee traffic around here but none showed up while I was getting screencaps. Wireshark did seem to do some decoding on it I think, but Zigbee isn't really what interests me anyway.

Happy sniffing :)

0 Comments

Leave a comment