diff --git a/net/batman-adv/distributed-arp-table.c b/net/batman-adv/distributed-arp-table.c
index 93e1fb04905539eac6ed247db1919f50dcf38ddc..997ae6ac51ff38f3247cc9e47bc9df43a26b0e47 100644
--- a/net/batman-adv/distributed-arp-table.c
+++ b/net/batman-adv/distributed-arp-table.c
@@ -1037,9 +1037,9 @@ bool batadv_dat_snoop_incoming_arp_request(struct batadv_priv *bat_priv,
 	if (hdr_size == sizeof(struct batadv_unicast_4addr_packet))
 		err = batadv_send_skb_via_tt_4addr(bat_priv, skb_new,
 						   BATADV_P_DAT_CACHE_REPLY,
-						   vid);
+						   NULL, vid);
 	else
-		err = batadv_send_skb_via_tt(bat_priv, skb_new, vid);
+		err = batadv_send_skb_via_tt(bat_priv, skb_new, NULL, vid);
 
 	if (err != NET_XMIT_DROP) {
 		batadv_inc_counter(bat_priv, BATADV_CNT_DAT_CACHED_REPLY_TX);
diff --git a/net/batman-adv/gateway_client.c b/net/batman-adv/gateway_client.c
index a5602ef0f262acd87d6cb3021cc03e7eebbfce07..4150a641c52ec0d3610a34c6f52650af57d0da5c 100644
--- a/net/batman-adv/gateway_client.c
+++ b/net/batman-adv/gateway_client.c
@@ -28,11 +28,17 @@
 #include <linux/udp.h>
 #include <linux/if_vlan.h>
 
-/* This is the offset of the options field in a dhcp packet starting at
- * the beginning of the dhcp header
+/* These are the offsets of the "hw type" and "hw address length" in the dhcp
+ * packet starting at the beginning of the dhcp header
  */
-#define BATADV_DHCP_OPTIONS_OFFSET 240
-#define BATADV_DHCP_REQUEST 3
+#define BATADV_DHCP_HTYPE_OFFSET	1
+#define BATADV_DHCP_HLEN_OFFSET		2
+/* Value of htype representing Ethernet */
+#define BATADV_DHCP_HTYPE_ETHERNET	0x01
+/* This is the offset of the "chaddr" field in the dhcp packet starting at the
+ * beginning of the dhcp header
+ */
+#define BATADV_DHCP_CHADDR_OFFSET	28
 
 static void batadv_gw_node_free_ref(struct batadv_gw_node *gw_node)
 {
@@ -596,80 +602,39 @@ int batadv_gw_client_seq_print_text(struct seq_file *seq, void *offset)
 	return 0;
 }
 
-/* this call might reallocate skb data */
-static bool batadv_is_type_dhcprequest(struct sk_buff *skb, int header_len)
-{
-	int ret = false;
-	unsigned char *p;
-	int pkt_len;
-
-	if (skb_linearize(skb) < 0)
-		goto out;
-
-	pkt_len = skb_headlen(skb);
-
-	if (pkt_len < header_len + BATADV_DHCP_OPTIONS_OFFSET + 1)
-		goto out;
-
-	p = skb->data + header_len + BATADV_DHCP_OPTIONS_OFFSET;
-	pkt_len -= header_len + BATADV_DHCP_OPTIONS_OFFSET + 1;
-
-	/* Access the dhcp option lists. Each entry is made up by:
-	 * - octet 1: option type
-	 * - octet 2: option data len (only if type != 255 and 0)
-	 * - octet 3: option data
-	 */
-	while (*p != 255 && !ret) {
-		/* p now points to the first octet: option type */
-		if (*p == 53) {
-			/* type 53 is the message type option.
-			 * Jump the len octet and go to the data octet
-			 */
-			if (pkt_len < 2)
-				goto out;
-			p += 2;
-
-			/* check if the message type is what we need */
-			if (*p == BATADV_DHCP_REQUEST)
-				ret = true;
-			break;
-		} else if (*p == 0) {
-			/* option type 0 (padding), just go forward */
-			if (pkt_len < 1)
-				goto out;
-			pkt_len--;
-			p++;
-		} else {
-			/* This is any other option. So we get the length... */
-			if (pkt_len < 1)
-				goto out;
-			pkt_len--;
-			p++;
-
-			/* ...and then we jump over the data */
-			if (pkt_len < 1 + (*p))
-				goto out;
-			pkt_len -= 1 + (*p);
-			p += 1 + (*p);
-		}
-	}
-out:
-	return ret;
-}
-
-/* this call might reallocate skb data */
-bool batadv_gw_is_dhcp_target(struct sk_buff *skb, unsigned int *header_len)
+/**
+ * batadv_gw_dhcp_recipient_get - check if a packet is a DHCP message
+ * @skb: the packet to check
+ * @header_len: a pointer to the batman-adv header size
+ * @chaddr: buffer where the client address will be stored. Valid
+ *  only if the function returns BATADV_DHCP_TO_CLIENT
+ *
+ * Returns:
+ * - BATADV_DHCP_NO if the packet is not a dhcp message or if there was an error
+ *   while parsing it
+ * - BATADV_DHCP_TO_SERVER if this is a message going to the DHCP server
+ * - BATADV_DHCP_TO_CLIENT if this is a message going to a DHCP client
+ *
+ * This function may re-allocate the data buffer of the skb passed as argument.
+ */
+enum batadv_dhcp_recipient
+batadv_gw_dhcp_recipient_get(struct sk_buff *skb, unsigned int *header_len,
+			     uint8_t *chaddr)
 {
+	enum batadv_dhcp_recipient ret = BATADV_DHCP_NO;
 	struct ethhdr *ethhdr;
 	struct iphdr *iphdr;
 	struct ipv6hdr *ipv6hdr;
 	struct udphdr *udphdr;
 	struct vlan_ethhdr *vhdr;
+	int chaddr_offset;
 	__be16 proto;
+	uint8_t *p;
 
 	/* check for ethernet header */
 	if (!pskb_may_pull(skb, *header_len + ETH_HLEN))
-		return false;
+		return BATADV_DHCP_NO;
+
 	ethhdr = (struct ethhdr *)skb->data;
 	proto = ethhdr->h_proto;
 	*header_len += ETH_HLEN;
@@ -677,7 +642,7 @@ bool batadv_gw_is_dhcp_target(struct sk_buff *skb, unsigned int *header_len)
 	/* check for initial vlan header */
 	if (proto == htons(ETH_P_8021Q)) {
 		if (!pskb_may_pull(skb, *header_len + VLAN_HLEN))
-			return false;
+			return BATADV_DHCP_NO;
 
 		vhdr = (struct vlan_ethhdr *)skb->data;
 		proto = vhdr->h_vlan_encapsulated_proto;
@@ -688,32 +653,34 @@ bool batadv_gw_is_dhcp_target(struct sk_buff *skb, unsigned int *header_len)
 	switch (proto) {
 	case htons(ETH_P_IP):
 		if (!pskb_may_pull(skb, *header_len + sizeof(*iphdr)))
-			return false;
+			return BATADV_DHCP_NO;
+
 		iphdr = (struct iphdr *)(skb->data + *header_len);
 		*header_len += iphdr->ihl * 4;
 
 		/* check for udp header */
 		if (iphdr->protocol != IPPROTO_UDP)
-			return false;
+			return BATADV_DHCP_NO;
 
 		break;
 	case htons(ETH_P_IPV6):
 		if (!pskb_may_pull(skb, *header_len + sizeof(*ipv6hdr)))
-			return false;
+			return BATADV_DHCP_NO;
+
 		ipv6hdr = (struct ipv6hdr *)(skb->data + *header_len);
 		*header_len += sizeof(*ipv6hdr);
 
 		/* check for udp header */
 		if (ipv6hdr->nexthdr != IPPROTO_UDP)
-			return false;
+			return BATADV_DHCP_NO;
 
 		break;
 	default:
-		return false;
+		return BATADV_DHCP_NO;
 	}
 
 	if (!pskb_may_pull(skb, *header_len + sizeof(*udphdr)))
-		return false;
+		return BATADV_DHCP_NO;
 
 	/* skb->data might have been reallocated by pskb_may_pull() */
 	ethhdr = (struct ethhdr *)skb->data;
@@ -724,17 +691,40 @@ bool batadv_gw_is_dhcp_target(struct sk_buff *skb, unsigned int *header_len)
 	*header_len += sizeof(*udphdr);
 
 	/* check for bootp port */
-	if ((proto == htons(ETH_P_IP)) &&
-	    (udphdr->dest != htons(67)))
-		return false;
+	switch (proto) {
+	case htons(ETH_P_IP):
+		if (udphdr->dest == htons(67))
+			ret = BATADV_DHCP_TO_SERVER;
+		else if (udphdr->source == htons(67))
+			ret = BATADV_DHCP_TO_CLIENT;
+		break;
+	case htons(ETH_P_IPV6):
+		if (udphdr->dest == htons(547))
+			ret = BATADV_DHCP_TO_SERVER;
+		else if (udphdr->source == htons(547))
+			ret = BATADV_DHCP_TO_CLIENT;
+		break;
+	}
 
-	if ((proto == htons(ETH_P_IPV6)) &&
-	    (udphdr->dest != htons(547)))
-		return false;
+	chaddr_offset = *header_len + BATADV_DHCP_CHADDR_OFFSET;
+	/* store the client address if the message is going to a client */
+	if (ret == BATADV_DHCP_TO_CLIENT &&
+	    pskb_may_pull(skb, chaddr_offset + ETH_ALEN)) {
+		/* check if the DHCP packet carries an Ethernet DHCP */
+		p = skb->data + *header_len + BATADV_DHCP_HTYPE_OFFSET;
+		if (*p != BATADV_DHCP_HTYPE_ETHERNET)
+			return BATADV_DHCP_NO;
+
+		/* check if the DHCP packet carries a valid Ethernet address */
+		p = skb->data + *header_len + BATADV_DHCP_HLEN_OFFSET;
+		if (*p != ETH_ALEN)
+			return BATADV_DHCP_NO;
+
+		memcpy(chaddr, skb->data + chaddr_offset, ETH_ALEN);
+	}
 
-	return true;
+	return ret;
 }
-
 /**
  * batadv_gw_out_of_range - check if the dhcp request destination is the best gw
  * @bat_priv: the bat priv with all the soft interface information
@@ -748,6 +738,7 @@ bool batadv_gw_is_dhcp_target(struct sk_buff *skb, unsigned int *header_len)
  * false otherwise.
  *
  * This call might reallocate skb data.
+ * Must be invoked only when the DHCP packet is going TO a DHCP SERVER.
  */
 bool batadv_gw_out_of_range(struct batadv_priv *bat_priv,
 			    struct sk_buff *skb)
@@ -755,19 +746,13 @@ bool batadv_gw_out_of_range(struct batadv_priv *bat_priv,
 	struct batadv_neigh_node *neigh_curr = NULL, *neigh_old = NULL;
 	struct batadv_orig_node *orig_dst_node = NULL;
 	struct batadv_gw_node *gw_node = NULL, *curr_gw = NULL;
-	struct ethhdr *ethhdr;
-	bool ret, out_of_range = false;
-	unsigned int header_len = 0;
+	struct ethhdr *ethhdr = (struct ethhdr *)skb->data;
+	bool out_of_range = false;
 	uint8_t curr_tq_avg;
 	unsigned short vid;
 
 	vid = batadv_get_vid(skb, 0);
 
-	ret = batadv_gw_is_dhcp_target(skb, &header_len);
-	if (!ret)
-		goto out;
-
-	ethhdr = (struct ethhdr *)skb->data;
 	orig_dst_node = batadv_transtable_search(bat_priv, ethhdr->h_source,
 						 ethhdr->h_dest, vid);
 	if (!orig_dst_node)
@@ -777,10 +762,6 @@ bool batadv_gw_out_of_range(struct batadv_priv *bat_priv,
 	if (!gw_node->bandwidth_down == 0)
 		goto out;
 
-	ret = batadv_is_type_dhcprequest(skb, header_len);
-	if (!ret)
-		goto out;
-
 	switch (atomic_read(&bat_priv->gw_mode)) {
 	case BATADV_GW_MODE_SERVER:
 		/* If we are a GW then we are our best GW. We can artificially
diff --git a/net/batman-adv/gateway_client.h b/net/batman-adv/gateway_client.h
index 514cff68890cb1cb4cc70ef94febb0cdfa8695dd..006befb8c7b85fe72b8a7a76c01b1d19f002b098 100644
--- a/net/batman-adv/gateway_client.h
+++ b/net/batman-adv/gateway_client.h
@@ -32,7 +32,9 @@ void batadv_gw_node_delete(struct batadv_priv *bat_priv,
 			   struct batadv_orig_node *orig_node);
 void batadv_gw_node_purge(struct batadv_priv *bat_priv);
 int batadv_gw_client_seq_print_text(struct seq_file *seq, void *offset);
-bool batadv_gw_is_dhcp_target(struct sk_buff *skb, unsigned int *header_len);
 bool batadv_gw_out_of_range(struct batadv_priv *bat_priv, struct sk_buff *skb);
+enum batadv_dhcp_recipient
+batadv_gw_dhcp_recipient_get(struct sk_buff *skb, unsigned int *header_len,
+			     uint8_t *chaddr);
 
 #endif /* _NET_BATMAN_ADV_GATEWAY_CLIENT_H_ */
diff --git a/net/batman-adv/send.c b/net/batman-adv/send.c
index cea6578ee7bed13e108814e477f1f86164a64f97..30d12c445ea3bb5b31f86380caeeae07454c6b1b 100644
--- a/net/batman-adv/send.c
+++ b/net/batman-adv/send.c
@@ -319,13 +319,23 @@ static int batadv_send_skb_unicast(struct batadv_priv *bat_priv,
  */
 int batadv_send_skb_via_tt_generic(struct batadv_priv *bat_priv,
 				   struct sk_buff *skb, int packet_type,
-				   int packet_subtype, unsigned short vid)
+				   int packet_subtype, uint8_t *dst_hint,
+				   unsigned short vid)
 {
 	struct ethhdr *ethhdr = (struct ethhdr *)skb->data;
 	struct batadv_orig_node *orig_node;
+	uint8_t *src, *dst;
+
+	src = ethhdr->h_source;
+	dst = ethhdr->h_dest;
+
+	/* if we got an hint! let's send the packet to this client (if any) */
+	if (dst_hint) {
+		src = NULL;
+		dst = dst_hint;
+	}
+	orig_node = batadv_transtable_search(bat_priv, src, dst, vid);
 
-	orig_node = batadv_transtable_search(bat_priv, ethhdr->h_source,
-					     ethhdr->h_dest, vid);
 	return batadv_send_skb_unicast(bat_priv, skb, packet_type,
 				       packet_subtype, orig_node, vid);
 }
diff --git a/net/batman-adv/send.h b/net/batman-adv/send.h
index f6d125e2a15bcacabc782015881ee1e11e43c417..0b17f8d56f2e078afbd56faf780ffddcc7479edd 100644
--- a/net/batman-adv/send.h
+++ b/net/batman-adv/send.h
@@ -38,7 +38,8 @@ bool batadv_send_skb_prepare_unicast_4addr(struct batadv_priv *bat_priv,
 					   int packet_subtype);
 int batadv_send_skb_via_tt_generic(struct batadv_priv *bat_priv,
 				   struct sk_buff *skb, int packet_type,
-				   int packet_subtype, unsigned short vid);
+				   int packet_subtype, uint8_t *dst_hint,
+				   unsigned short vid);
 int batadv_send_skb_via_gw(struct batadv_priv *bat_priv, struct sk_buff *skb,
 			   unsigned short vid);
 
@@ -55,11 +56,11 @@ int batadv_send_skb_via_gw(struct batadv_priv *bat_priv, struct sk_buff *skb,
  * Returns NET_XMIT_DROP in case of error or NET_XMIT_SUCCESS otherwise.
  */
 static inline int batadv_send_skb_via_tt(struct batadv_priv *bat_priv,
-					 struct sk_buff *skb,
+					 struct sk_buff *skb, uint8_t *dst_hint,
 					 unsigned short vid)
 {
 	return batadv_send_skb_via_tt_generic(bat_priv, skb, BATADV_UNICAST, 0,
-					      vid);
+					      dst_hint, vid);
 }
 
 /**
@@ -79,11 +80,12 @@ static inline int batadv_send_skb_via_tt(struct batadv_priv *bat_priv,
 static inline int batadv_send_skb_via_tt_4addr(struct batadv_priv *bat_priv,
 					       struct sk_buff *skb,
 					       int packet_subtype,
+					       uint8_t *dst_hint,
 					       unsigned short vid)
 {
 	return batadv_send_skb_via_tt_generic(bat_priv, skb,
 					      BATADV_UNICAST_4ADDR,
-					      packet_subtype, vid);
+					      packet_subtype, dst_hint, vid);
 }
 
 #endif /* _NET_BATMAN_ADV_SEND_H_ */
diff --git a/net/batman-adv/soft-interface.c b/net/batman-adv/soft-interface.c
index 3686be7805f3be7db26e0c7e7d4ad00477984fc5..b569932ddcc40eb129c43d2b939ff85c5fe8b71f 100644
--- a/net/batman-adv/soft-interface.c
+++ b/net/batman-adv/soft-interface.c
@@ -160,6 +160,8 @@ static int batadv_interface_tx(struct sk_buff *skb,
 						   0x00, 0x00};
 	static const uint8_t ectp_addr[ETH_ALEN] = {0xCF, 0x00, 0x00, 0x00,
 						    0x00, 0x00};
+	enum batadv_dhcp_recipient dhcp_rcp = BATADV_DHCP_NO;
+	uint8_t *dst_hint = NULL, chaddr[ETH_ALEN];
 	struct vlan_ethhdr *vhdr;
 	unsigned int header_len = 0;
 	int data_len = skb->len, ret;
@@ -167,6 +169,7 @@ static int batadv_interface_tx(struct sk_buff *skb,
 	bool do_bcast = false, client_added;
 	unsigned short vid;
 	uint32_t seqno;
+	int gw_mode;
 
 	if (atomic_read(&bat_priv->mesh_state) != BATADV_MESH_ACTIVE)
 		goto dropped;
@@ -213,36 +216,39 @@ static int batadv_interface_tx(struct sk_buff *skb,
 	if (batadv_compare_eth(ethhdr->h_dest, ectp_addr))
 		goto dropped;
 
+	gw_mode = atomic_read(&bat_priv->gw_mode);
 	if (is_multicast_ether_addr(ethhdr->h_dest)) {
-		do_bcast = true;
-
-		switch (atomic_read(&bat_priv->gw_mode)) {
-		case BATADV_GW_MODE_SERVER:
-			/* gateway servers should not send dhcp
-			 * requests into the mesh
-			 */
-			ret = batadv_gw_is_dhcp_target(skb, &header_len);
-			if (ret)
-				goto dropped;
-			break;
-		case BATADV_GW_MODE_CLIENT:
-			/* gateway clients should send dhcp requests
-			 * via unicast to their gateway
-			 */
-			ret = batadv_gw_is_dhcp_target(skb, &header_len);
-			if (ret)
-				do_bcast = false;
-			break;
-		case BATADV_GW_MODE_OFF:
-		default:
-			break;
+		/* if gw mode is off, broadcast every packet */
+		if (gw_mode == BATADV_GW_MODE_OFF) {
+			do_bcast = true;
+			goto send;
 		}
 
-		/* reminder: ethhdr might have become unusable from here on
-		 * (batadv_gw_is_dhcp_target() might have reallocated skb data)
+		dhcp_rcp = batadv_gw_dhcp_recipient_get(skb, &header_len,
+							chaddr);
+		/* skb->data may have been modified by
+		 * batadv_gw_dhcp_recipient_get()
 		 */
+		ethhdr = (struct ethhdr *)skb->data;
+		/* if gw_mode is on, broadcast any non-DHCP message.
+		 * All the DHCP packets are going to be sent as unicast
+		 */
+		if (dhcp_rcp == BATADV_DHCP_NO) {
+			do_bcast = true;
+			goto send;
+		}
+
+		if (dhcp_rcp == BATADV_DHCP_TO_CLIENT)
+			dst_hint = chaddr;
+		else if ((gw_mode == BATADV_GW_MODE_SERVER) &&
+			 (dhcp_rcp == BATADV_DHCP_TO_SERVER))
+			/* gateways should not forward any DHCP message if
+			 * directed to a DHCP server
+			 */
+			goto dropped;
 	}
 
+send:
 	batadv_skb_set_priority(skb, 0);
 
 	/* ethernet packet should be broadcasted */
@@ -288,22 +294,22 @@ static int batadv_interface_tx(struct sk_buff *skb,
 
 	/* unicast packet */
 	} else {
-		if (atomic_read(&bat_priv->gw_mode) != BATADV_GW_MODE_OFF) {
+		/* DHCP packets going to a server will use the GW feature */
+		if (dhcp_rcp == BATADV_DHCP_TO_SERVER) {
 			ret = batadv_gw_out_of_range(bat_priv, skb);
 			if (ret)
 				goto dropped;
-		}
-
-		if (batadv_dat_snoop_outgoing_arp_request(bat_priv, skb))
-			goto dropped;
-
-		batadv_dat_snoop_outgoing_arp_reply(bat_priv, skb);
-
-		if (is_multicast_ether_addr(ethhdr->h_dest))
 			ret = batadv_send_skb_via_gw(bat_priv, skb, vid);
-		else
-			ret = batadv_send_skb_via_tt(bat_priv, skb, vid);
+		} else {
+			if (batadv_dat_snoop_outgoing_arp_request(bat_priv,
+								  skb))
+				goto dropped;
 
+			batadv_dat_snoop_outgoing_arp_reply(bat_priv, skb);
+
+			ret = batadv_send_skb_via_tt(bat_priv, skb, dst_hint,
+						     vid);
+		}
 		if (ret == NET_XMIT_DROP)
 			goto dropped_freed;
 	}
diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
index e545ac972433dde0bd441b47959179e9458d7ef4..70abb1c7087f7aa1e0688948a1732fe74f8ce076 100644
--- a/net/batman-adv/types.h
+++ b/net/batman-adv/types.h
@@ -33,6 +33,18 @@
 
 #endif /* CONFIG_BATMAN_ADV_DAT */
 
+/**
+ * enum batadv_dhcp_recipient - dhcp destination
+ * @BATADV_DHCP_NO: packet is not a dhcp message
+ * @BATADV_DHCP_TO_SERVER: dhcp message is directed to a server
+ * @BATADV_DHCP_TO_CLIENT: dhcp message is directed to a client
+ */
+enum batadv_dhcp_recipient {
+	BATADV_DHCP_NO = 0,
+	BATADV_DHCP_TO_SERVER,
+	BATADV_DHCP_TO_CLIENT,
+};
+
 /**
  * BATADV_TT_REMOTE_MASK - bitmask selecting the flags that are sent over the
  *  wire only