diff mbox

[RFC,bluetooth-next,18/19] ndisc: add support for short address option

Message ID 1458652515-7862-19-git-send-email-aar@pengutronix.de (mailing list archive)
State Superseded
Headers show

Commit Message

Alexander Aring March 22, 2016, 1:15 p.m. UTC
This patch will add 802.15.4 6lowpan handling when parsing NS and NA
messages. The 802.15.4 6lowpan neighbour has a parivate data room for
storing an optional address which is available via link layer address
option fields.

This option field will currently always be written if a neighbour was
returned by neighbour lookup functionality.

Signed-off-by: Alexander Aring <aar@pengutronix.de>
---
 net/ipv6/ndisc.c | 126 ++++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 121 insertions(+), 5 deletions(-)
diff mbox

Patch

diff --git a/net/ipv6/ndisc.c b/net/ipv6/ndisc.c
index 5c96ec6..6d9768e 100644
--- a/net/ipv6/ndisc.c
+++ b/net/ipv6/ndisc.c
@@ -235,6 +235,52 @@  static void ndisc_802154_parse_addr_options(struct ndisc_options *ndopts,
 		break;
 	}
 }
+
+static void ndisc_802154_neigh_update(struct neighbour *n, void *priv)
+{
+	struct lowpan_802154_neigh *neigh = lowpan_802154_neigh(neighbour_priv(n));
+
+	write_lock_bh(&n->lock);
+	if (priv)
+		ieee802154_be16_to_le16(&neigh->short_addr, priv);
+	else
+		neigh->short_addr = cpu_to_le16(IEEE802154_ADDR_SHORT_UNSPEC);
+	write_unlock_bh(&n->lock);
+}
+
+static inline int ndisc_802154_short_addr_space(struct net_device *dev)
+{
+	struct wpan_dev *wpan_dev;
+	int addr_space = 0;
+
+	if (lowpan_is_ll(dev, LOWPAN_LLTYPE_IEEE802154)) {
+		wpan_dev = lowpan_802154_dev(dev)->wdev->ieee802154_ptr;
+
+		if (ieee802154_is_valid_src_short_addr(wpan_dev->short_addr))
+			addr_space = ndisc_opt_addr_space(dev, IEEE802154_SHORT_ADDR_LEN);
+	}
+
+	return addr_space;
+}
+
+static inline void ndisc_802154_short_addr_option(struct net_device *dev,
+						  struct sk_buff *skb,
+						  int type)
+{
+	struct wpan_dev *wpan_dev;
+	__be16 short_addr;
+
+	if (lowpan_is_ll(dev, LOWPAN_LLTYPE_IEEE802154)) {
+		wpan_dev = lowpan_802154_dev(dev)->wdev->ieee802154_ptr;
+
+		if (ieee802154_is_valid_src_short_addr(wpan_dev->short_addr)) {
+			ieee802154_le16_to_be16(&short_addr,
+						&wpan_dev->short_addr);
+			ndisc_fill_addr_option(skb, type, &short_addr,
+					       IEEE802154_SHORT_ADDR_LEN);
+		}
+	}
+}
 #endif
 
 struct ndisc_options *ndisc_parse_options(const struct net_device *dev,
@@ -550,8 +596,12 @@  void ndisc_send_na(struct net_device *dev, const struct in6_addr *daddr,
 
 	if (!dev->addr_len)
 		inc_opt = 0;
-	if (inc_opt)
+	if (inc_opt) {
 		optlen += ndisc_opt_addr_space(dev, dev->addr_len);
+#ifdef CONFIG_IEEE802154_6LOWPAN
+		optlen += ndisc_802154_short_addr_space(dev);
+#endif
+	}
 
 	skb = ndisc_alloc_skb(dev, sizeof(*msg) + optlen);
 	if (!skb)
@@ -568,9 +618,14 @@  void ndisc_send_na(struct net_device *dev, const struct in6_addr *daddr,
 		.target = *solicited_addr,
 	};
 
-	if (inc_opt)
+	if (inc_opt) {
 		ndisc_fill_addr_option(skb, ND_OPT_TARGET_LL_ADDR,
 				       dev->dev_addr, dev->addr_len);
+#ifdef CONFIG_IEEE802154_6LOWPAN
+		ndisc_802154_short_addr_option(dev, skb,
+					       ND_OPT_TARGET_LL_ADDR);
+#endif
+	}
 
 
 	ndisc_send_skb(skb, daddr, src_addr);
@@ -615,8 +670,12 @@  void ndisc_send_ns(struct net_device *dev, const struct in6_addr *solicit,
 
 	if (ipv6_addr_any(saddr))
 		inc_opt = false;
-	if (inc_opt)
+	if (inc_opt) {
 		optlen += ndisc_opt_addr_space(dev, dev->addr_len);
+#ifdef CONFIG_IEEE802154_6LOWPAN
+		optlen += ndisc_802154_short_addr_space(dev);
+#endif
+	}
 
 	skb = ndisc_alloc_skb(dev, sizeof(*msg) + optlen);
 	if (!skb)
@@ -630,9 +689,14 @@  void ndisc_send_ns(struct net_device *dev, const struct in6_addr *solicit,
 		.target = *solicit,
 	};
 
-	if (inc_opt)
+	if (inc_opt) {
 		ndisc_fill_addr_option(skb, ND_OPT_SOURCE_LL_ADDR,
 				       dev->dev_addr, dev->addr_len);
+#ifdef CONFIG_IEEE802154_6LOWPAN
+		ndisc_802154_short_addr_option(dev, skb,
+					       ND_OPT_SOURCE_LL_ADDR);
+#endif
+	}
 
 	ndisc_send_skb(skb, daddr, saddr);
 }
@@ -760,6 +824,9 @@  static void ndisc_recv_ns(struct sk_buff *skb)
 	int dad = ipv6_addr_any(saddr);
 	bool inc;
 	int is_router = -1;
+#ifdef CONFIG_IEEE802154_6LOWPAN
+	u8 *lladdr_short = NULL;
+#endif
 
 	if (skb->len < sizeof(struct nd_msg)) {
 		ND_PRINTK(2, warn, "NS: packet too short\n");
@@ -806,6 +873,30 @@  static void ndisc_recv_ns(struct sk_buff *skb)
 		}
 	}
 
+#ifdef CONFIG_IEEE802154_6LOWPAN
+	if (lowpan_is_ll(dev, LOWPAN_LLTYPE_IEEE802154) &&
+	    ndopts.nd_802154_opts_src_lladdr) {
+		lladdr_short = ndisc_opt_addr_data(ndopts.nd_802154_opts_src_lladdr,
+						   dev, IEEE802154_SHORT_ADDR_LEN);
+		if (!lladdr_short) {
+			ND_PRINTK(2, warn,
+				  "NS: invalid short link-layer address length\n");
+			return;
+		}
+
+		/* RFC2461 7.1.1:
+		 *	If the IP source address is the unspecified address,
+		 *	there MUST NOT be source link-layer address option
+		 *	in the message.
+		 */
+		if (dad) {
+			ND_PRINTK(2, warn,
+				  "NS: bad DAD packet (short link-layer address option)\n");
+			return;
+		}
+	}
+#endif
+
 	inc = ipv6_addr_is_multicast(daddr);
 
 	ifp = ipv6_get_ifaddr(dev_net(dev), &msg->target, dev, 1);
@@ -898,10 +989,15 @@  have_ifp:
 	 */
 	neigh = __neigh_lookup(&nd_tbl, saddr, dev,
 			       !inc || lladdr || !dev->addr_len);
-	if (neigh)
+	if (neigh) {
 		neigh_update(neigh, lladdr, NUD_STALE,
 			     NEIGH_UPDATE_F_WEAK_OVERRIDE|
 			     NEIGH_UPDATE_F_OVERRIDE);
+#ifdef CONFIG_IEEE802154_6LOWPAN
+		if (lowpan_is_ll(dev, LOWPAN_LLTYPE_IEEE802154))
+			ndisc_802154_neigh_update(neigh, lladdr_short);
+#endif
+	}
 	if (neigh || !dev->header_ops) {
 		ndisc_send_na(dev, saddr, &msg->target, !!is_router,
 			      true, (ifp != NULL && inc), inc);
@@ -929,6 +1025,9 @@  static void ndisc_recv_na(struct sk_buff *skb)
 	struct inet6_dev *idev = __in6_dev_get(dev);
 	struct inet6_ifaddr *ifp;
 	struct neighbour *neigh;
+#ifdef CONFIG_IEEE802154_6LOWPAN
+	u8 *lladdr_short = NULL;
+#endif
 
 	if (skb->len < sizeof(struct nd_msg)) {
 		ND_PRINTK(2, warn, "NA: packet too short\n");
@@ -967,6 +1066,18 @@  static void ndisc_recv_na(struct sk_buff *skb)
 			return;
 		}
 	}
+#ifdef CONFIG_IEEE802154_6LOWPAN
+	if (lowpan_is_ll(dev, LOWPAN_LLTYPE_IEEE802154) &&
+	    ndopts.nd_802154_opts_tgt_lladdr) {
+		lladdr_short = ndisc_opt_addr_data(ndopts.nd_802154_opts_tgt_lladdr,
+						   dev, IEEE802154_SHORT_ADDR_LEN);
+		if (!lladdr_short) {
+			ND_PRINTK(2, warn,
+				  "NA: invalid short link-layer address length\n");
+			return;
+		}
+	}
+#endif
 	ifp = ipv6_get_ifaddr(dev_net(dev), &msg->target, dev, 1);
 	if (ifp) {
 		if (skb->pkt_type != PACKET_LOOPBACK
@@ -1018,6 +1129,11 @@  static void ndisc_recv_na(struct sk_buff *skb)
 			     NEIGH_UPDATE_F_OVERRIDE_ISROUTER|
 			     (msg->icmph.icmp6_router ? NEIGH_UPDATE_F_ISROUTER : 0));
 
+#ifdef CONFIG_IEEE802154_6LOWPAN
+		if (lowpan_is_ll(dev, LOWPAN_LLTYPE_IEEE802154))
+			ndisc_802154_neigh_update(neigh, lladdr_short);
+#endif
+
 		if ((old_flags & ~neigh->flags) & NTF_ROUTER) {
 			/*
 			 * Change: router to host