diff mbox

[v2,3/3] Add address masks to BSSID lists

Message ID 20150105201016.GE15168@zirkel.wertarbyte.de
State Accepted
Headers show

Commit Message

Stefan Tomanek Jan. 5, 2015, 8:10 p.m. UTC
In many applications it is useful not just to enumerate a group of well known
access points, but to use a address/mask notation to match an entire set of
addresses (ca:ff:ee:00:00:00/FF:FF:FF:00:00:00).

This change expands the data structures used by mac lists to include a mask
indicating the significant (non-masked) portions of an address and extends the
list parser to recognize mask suffixes.

Signed-off-by: Stefan Tomanek <stefan.tomanek@wertarbyte.de>
---
 src/utils/common.c                 | 83 ++++++++++++++++++++++++++++++++------
 src/utils/common.h                 |  3 ++
 wpa_supplicant/config.c            | 22 +++++-----
 wpa_supplicant/events.c            | 15 ++++++-
 wpa_supplicant/wpa_supplicant.conf |  4 +-
 5 files changed, 101 insertions(+), 26 deletions(-)

Comments

Jouni Malinen Jan. 10, 2015, 4:03 p.m. UTC | #1
On Mon, Jan 05, 2015 at 09:10:16PM +0100, Stefan Tomanek wrote:
> In many applications it is useful not just to enumerate a group of well known
> access points, but to use a address/mask notation to match an entire set of
> addresses (ca:ff:ee:00:00:00/FF:FF:FF:00:00:00).
> 
> This change expands the data structures used by mac lists to include a mask
> indicating the significant (non-masked) portions of an address and extends the
> list parser to recognize mask suffixes.

Thanks, applied with number of fixes.
diff mbox

Patch

diff --git a/src/utils/common.c b/src/utils/common.c
index 182c6a8..ff6d19d 100644
--- a/src/utils/common.c
+++ b/src/utils/common.c
@@ -35,6 +35,20 @@  int hex2byte(const char *hex)
 	return (a << 4) | b;
 }
 
+static const char *hwaddr_parse(const char *txt, u8 *addr) {
+	size_t i;
+	for (i = 0; i < ETH_ALEN; i++) {
+		int a = 0;
+		a = hex2byte(txt);
+		if (a < 0)
+			return NULL;
+		txt += 2;
+		addr[i] = a;
+		if (i < ETH_ALEN-1 && *txt++ != ':')
+			return NULL;
+	}
+	return txt;
+}
 
 /**
  * hwaddr_aton - Convert ASCII string to MAC address (colon-delimited format)
@@ -44,22 +58,39 @@  int hex2byte(const char *hex)
  */
 int hwaddr_aton(const char *txt, u8 *addr)
 {
-	int i;
+	return hwaddr_parse(txt, addr) ? 0 : -1;
+}
 
-	for (i = 0; i < 6; i++) {
-		int a, b;
+/**
+ * hwaddr_masked_aton - Convert ASCII string with optional mask to MAC address (colon-delimited format)
+ * @txt: MAC address with optional mask as a string (e.g., "00:11:22:33:44:55/FF:FF:FF:FF:00:00")
+ * @addr: Buffer for the MAC address (ETH_ALEN = 6 bytes)
+ * @mask: Buffer for the MAC address mask (ETH_ALEN = 6 bytes)
+ * @maskable: Flag to indicate whether a mask is allowed
+ * Returns: 0 on success, -1 on failure (e.g., string not a MAC address)
+ */
+int hwaddr_masked_aton(const char *txt, u8 *addr, u8 *mask, u8 maskable)
+{
+	const char *r;
+	/* parse address part */
+	r = hwaddr_parse(txt, addr);
+	if (!r)
+		return -1;
 
-		a = hex2num(*txt++);
-		if (a < 0)
-			return -1;
-		b = hex2num(*txt++);
-		if (b < 0)
-			return -1;
-		*addr++ = (a << 4) | b;
-		if (i < 5 && *txt++ != ':')
+	/* check for optional mask */
+	if (! *r || isspace(*r)) {
+		/* no mask specified, assume default */
+		os_memset(mask, 0xFF, ETH_ALEN);
+	} else if (maskable && *r == '/') {
+		/* mask specified and allowed */
+		r = hwaddr_parse(r+1, mask);
+		/* parser error? */
+		if (!r)
 			return -1;
+	} else {
+		/* mask specified but not allowed or trailing garbage */
+		return -1;
 	}
-
 	return 0;
 }
 
@@ -143,6 +174,34 @@  int hexstr2bin(const char *hex, u8 *buf, size_t len)
 	return 0;
 }
 
+static char *hwaddr_txt(char *buf, u8 *addr) {
+	size_t i;
+	for (i = 0; i < ETH_ALEN; i++) {
+		buf += sprintf(buf, "%02x", addr[i]);
+		if (i < ETH_ALEN-1)
+			*buf++ = ':';
+	}
+	return buf;
+}
+
+size_t hwaddr_mask_txt(char *buf, u8 *addr, u8 *mask) {
+	size_t i;
+	char *pos = buf;
+	int print_mask = 0;
+	for (i = 0; i < ETH_ALEN; i++) {
+		if (mask[i] != 0xFF) {
+			print_mask = 1;
+			break;
+		}
+	}
+	pos = hwaddr_txt(pos, addr);
+	if (print_mask) {
+		*pos++ = '/';
+		pos = hwaddr_txt(pos, mask);
+	}
+	*pos = '\0';
+	return pos - buf;
+}
 
 /**
  * inc_byte_array - Increment arbitrary length byte array by one
diff --git a/src/utils/common.h b/src/utils/common.h
index 7eca409..8ccebfb 100644
--- a/src/utils/common.h
+++ b/src/utils/common.h
@@ -471,6 +471,7 @@  typedef u64 __bitwise le64;
 #endif /* __must_check */
 
 int hwaddr_aton(const char *txt, u8 *addr);
+int hwaddr_masked_aton(const char *txt, u8 *addr, u8 *mask, u8 maskable);
 int hwaddr_compact_aton(const char *txt, u8 *addr);
 int hwaddr_aton2(const char *txt, u8 *addr);
 int hex2byte(const char *hex);
@@ -482,6 +483,8 @@  int wpa_snprintf_hex(char *buf, size_t buf_size, const u8 *data, size_t len);
 int wpa_snprintf_hex_uppercase(char *buf, size_t buf_size, const u8 *data,
 			       size_t len);
 
+size_t hwaddr_mask_txt(char *buf, u8 *addr, u8 *mask);
+
 #ifdef CONFIG_NATIVE_WINDOWS
 void wpa_unicode2ascii_inplace(TCHAR *str);
 TCHAR * wpa_strdup_tchar(const char *str);
diff --git a/wpa_supplicant/config.c b/wpa_supplicant/config.c
index d712522..427fe75 100644
--- a/wpa_supplicant/config.c
+++ b/wpa_supplicant/config.c
@@ -237,10 +237,11 @@  static char * wpa_config_write_int(const struct parse_data *data,
 static int wpa_config_parse_addr_list(const struct parse_data *data,
 				      int line, const char *value,
 				      u8 **list, size_t *num, char *name,
-				      u8 abort_on_error)
+				      u8 abort_on_error, u8 masked)
 {
 	const char *pos;
 	u8 *buf, *n, addr[ETH_ALEN];
+	u8 mask[ETH_ALEN];
 	size_t count;
 
 	buf = NULL;
@@ -251,7 +252,7 @@  static int wpa_config_parse_addr_list(const struct parse_data *data,
 		while (*pos == ' ')
 			pos++;
 
-		if (hwaddr_aton(pos, addr)) {
+		if (hwaddr_masked_aton(pos, addr, mask, masked)) {
 			if (abort_on_error || count == 0) {
 				wpa_printf(MSG_ERROR, "Line %d: Invalid "
 					   "%s address '%s'.",
@@ -265,14 +266,15 @@  static int wpa_config_parse_addr_list(const struct parse_data *data,
 				   "truncated %s address '%s'",
 				   line, name, pos);
 		} else {
-			n = os_realloc_array(buf, count + 1, ETH_ALEN);
+			n = os_realloc_array(buf, count + 1, 2*ETH_ALEN);
 			if (n == NULL) {
 				os_free(buf);
 				return -1;
 			}
 			buf = n;
-			os_memmove(buf + ETH_ALEN, buf, count * ETH_ALEN);
+			os_memmove(buf + 2*ETH_ALEN, buf, count * 2*ETH_ALEN);
 			os_memcpy(buf, addr, ETH_ALEN);
+			os_memcpy(buf + ETH_ALEN, mask, ETH_ALEN);
 			count++;
 			wpa_hexdump(MSG_MSGDUMP, name, addr, ETH_ALEN);
 		}
@@ -305,9 +307,9 @@  static char * wpa_config_write_addr_list(const struct parse_data *data,
 	end = value + 20 * num;
 
 	for (i = num; i > 0; i--) {
-		res = os_snprintf(pos, end - pos, MACSTR " ",
-				  MAC2STR(list +
-					  (i - 1) * ETH_ALEN));
+		u8 *a = list + (i-1) * 2*ETH_ALEN;
+		u8 *m = list + (i-1) * 2*ETH_ALEN + ETH_ALEN;
+		res = hwaddr_mask_txt(pos, a, m);
 		if (os_snprintf_error(end - pos, res)) {
 			os_free(value);
 			return NULL;
@@ -373,7 +375,7 @@  static int wpa_config_parse_bssid_blacklist(const struct parse_data *data,
 	return wpa_config_parse_addr_list(data, line, value,
 	                                  &ssid->bssid_blacklist,
 	                                  &ssid->num_bssid_blacklist,
-	                                  "bssid_blacklist", 1);
+	                                  "bssid_blacklist", 1, 1);
 }
 
 #ifndef NO_CONFIG_WRITE
@@ -394,7 +396,7 @@  static int wpa_config_parse_bssid_whitelist(const struct parse_data *data,
 	return wpa_config_parse_addr_list(data, line, value,
 	                                  &ssid->bssid_whitelist,
 	                                  &ssid->num_bssid_whitelist,
-	                                  "bssid_whitelist", 1);
+	                                  "bssid_whitelist", 1, 1);
 }
 
 #ifndef NO_CONFIG_WRITE
@@ -1585,7 +1587,7 @@  static int wpa_config_parse_p2p_client_list(const struct parse_data *data,
 	return wpa_config_parse_addr_list(data, line, value,
 	                                  &ssid->p2p_client_list,
 	                                  &ssid->num_p2p_clients,
-	                                  "p2p_client_list", 0);
+	                                  "p2p_client_list", 0, 0);
 }
 
 
diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c
index 263b677..5f70be0 100644
--- a/wpa_supplicant/events.c
+++ b/wpa_supplicant/events.c
@@ -697,11 +697,22 @@  static int bss_is_ess(struct wpa_bss *bss)
 		IEEE80211_CAP_ESS);
 }
 
+static int match_mac_mask(u8 *addrA, u8 *addrB, u8 *mask) {
+	size_t i;
+	for (i = 0; i < ETH_ALEN; i++) {
+		if ((addrA[i] & mask[i]) != (addrB[i] & mask[i])) {
+			return 0;
+		}
+	}
+	return 1;
+}
+
 static int addr_in_list(u8 *addr, u8 *list, size_t num) {
 	size_t i;
 	for (i = 0; i < num; i++) {
-		u8 *a = list + (i*ETH_ALEN);
-		if (os_memcmp(a, addr, ETH_ALEN) == 0) {
+		u8 *a = list + (i*ETH_ALEN*2);
+		u8 *m = list + (i*ETH_ALEN*2) + ETH_ALEN;
+		if (match_mac_mask(a, addr, m)) {
 			return 1;
 		}
 	}
diff --git a/wpa_supplicant/wpa_supplicant.conf b/wpa_supplicant/wpa_supplicant.conf
index a80bf21..38bfce0 100644
--- a/wpa_supplicant/wpa_supplicant.conf
+++ b/wpa_supplicant/wpa_supplicant.conf
@@ -1440,11 +1440,11 @@  network={
 }
 
 # Example configuration limiting AP selection to a specific set of APs;
-# any other AP will be ignored for this network entry
+# any other AP not matching the masked address will be ignored
 network={
 	ssid="example"
 	psk="very secret passphrase"
-	bssid_whitelist=ca:fe:ba:be:d0:0d de:ad:be:ef:00:00
+	bssid_whitelist=ca:fe:ba:be:00:00/FF:FF:FF:FF:00:00 00:00:F0:0F:D0:0D/00:00:FF:FF:FF:FF
 }
 
 # Example config file that will only scan on channel 36.