[Patch v9 10/16] AP: Support Extended Key ID

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Support Extended Key ID in hostapd according to IEEE 802.11-2016.

Extended Key ID allows to rekey pairwise keys without the otherwise
unavoidable MPDU losses on a busy link. The standard is fully backward
compatible, allowing an AP to serve STAs with and without Extended Key
ID support in the same BSS.

Signed-off-by: Alexander Wetzel <alexander@xxxxxxxxxxxxxx>
---
 hostapd/config_file.c  |  9 +++++
 hostapd/hostapd.conf   | 11 +++++++
 src/ap/ap_config.c     | 10 ++++++
 src/ap/ap_config.h     |  1 +
 src/ap/wpa_auth.c      | 74 +++++++++++++++++++++++++++++++++++-------
 src/ap/wpa_auth.h      |  1 +
 src/ap/wpa_auth_glue.c | 22 ++++++++++++-
 src/ap/wpa_auth_i.h    |  3 ++
 src/ap/wpa_auth_ie.c   | 54 +++++++++++++++++++++++++++++-
 9 files changed, 171 insertions(+), 14 deletions(-)

diff --git a/hostapd/config_file.c b/hostapd/config_file.c
index b88071a58..417d4ee3c 100644
--- a/hostapd/config_file.c
+++ b/hostapd/config_file.c
@@ -2875,6 +2875,15 @@ static int hostapd_config_fill(struct hostapd_config *conf,
 		}
 	} else if (os_strcmp(buf, "wpa") == 0) {
 		bss->wpa = atoi(pos);
+	} else if (os_strcmp(buf, "wpa_extended_key_id") == 0) {
+		bss->wpa_extended_key_id = atoi(pos);
+		if (bss->wpa_extended_key_id < 0 ||
+		    bss->wpa_extended_key_id > 1) {
+			wpa_printf(MSG_ERROR,
+				   "Line %d: Invalid wpa_extended_key_id=%d; allowed range 0..1",
+				   line, bss->wpa_extended_key_id);
+			return 1;
+		}
 	} else if (os_strcmp(buf, "wpa_group_rekey") == 0) {
 		bss->wpa_group_rekey = atoi(pos);
 		bss->wpa_group_rekey_set = 1;
diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf
index b61176717..9de9096fd 100644
--- a/hostapd/hostapd.conf
+++ b/hostapd/hostapd.conf
@@ -1509,6 +1509,17 @@ own_ip_addr=127.0.0.1
 # wpa_key_mgmt=SAE for WPA3-Personal instead of wpa_key_mgmt=WPA-PSK).
 #wpa=2
 
+# Extended Key ID support based on IEEE 802.11-2016
+#
+# Extended Key ID allows to rekey PTK keys without the impacts the "normal"
+# PTK0 rekeying has. (No data losses during the rekey and it requires less
+# potentially missing special handling from the card/driver.)
+# But it can only be used with wpa=2, CCMP/GCMP ciphers and when the
+# card/driver has support for it.
+# 0 = force off
+# 1 = enable and use Extended Key ID support when possible (default)
+#wpa_extended_key_id=1
+
 # WPA pre-shared keys for WPA-PSK. This can be either entered as a 256-bit
 # secret in hex format (64 hex digits), wpa_psk, or as an ASCII passphrase
 # (8..63 characters) that will be converted to PSK. This conversion uses SSID
diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c
index 98a7f6e26..63174c133 100644
--- a/src/ap/ap_config.c
+++ b/src/ap/ap_config.c
@@ -62,6 +62,7 @@ void hostapd_config_defaults_bss(struct hostapd_bss_config *bss)
 	bss->broadcast_key_idx_max = 2;
 	bss->eap_reauth_period = 3600;
 
+	bss->wpa_extended_key_id = 1;
 	bss->wpa_group_rekey = 600;
 	bss->wpa_gmk_rekey = 86400;
 	bss->wpa_deny_ptk0_rekey = PTK0_REKEY_ALLOW_NEVER;
@@ -1143,6 +1144,15 @@ static int hostapd_config_check_bss(struct hostapd_bss_config *bss,
 		}
 	}
 
+	if (full_config && bss->wpa_extended_key_id &&
+	    !(bss->wpa & WPA_PROTO_RSN &&
+	      bss->rsn_pairwise & (WPA_CIPHER_CCMP | WPA_CIPHER_CCMP_256 |
+				   WPA_CIPHER_GCMP | WPA_CIPHER_GCMP_256))) {
+		wpa_printf(MSG_ERROR,
+			   "Extended Key ID support requires WPA2 and CCMP/GCMP, disabling it");
+		bss->wpa_extended_key_id = 0;
+	}
+
 #ifdef CONFIG_IEEE80211R_AP
 	if (full_config && wpa_key_mgmt_ft(bss->wpa_key_mgmt) &&
 	    (bss->nas_identifier == NULL ||
diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h
index 0b4483ea1..75f8dc432 100644
--- a/src/ap/ap_config.h
+++ b/src/ap/ap_config.h
@@ -345,6 +345,7 @@ struct hostapd_bss_config {
 			* algorithms, WPA_AUTH_ALG_{OPEN,SHARED,LEAP} */
 
 	int wpa; /* bitfield of WPA_PROTO_WPA, WPA_PROTO_RSN */
+	int wpa_extended_key_id;
 	int wpa_key_mgmt;
 	enum mfp_options ieee80211w;
 	int group_mgmt_cipher;
diff --git a/src/ap/wpa_auth.c b/src/ap/wpa_auth.c
index 7298e4928..c8ef366eb 100644
--- a/src/ap/wpa_auth.c
+++ b/src/ap/wpa_auth.c
@@ -756,7 +756,8 @@ static void wpa_request_new_ptk(struct wpa_state_machine *sm)
 	if (sm == NULL)
 		return;
 
-	if (sm->wpa_auth->conf.wpa_deny_ptk0_rekey) {
+	if (!sm->use_extended_key_id &&
+	    sm->wpa_auth->conf.wpa_deny_ptk0_rekey) {
 		wpa_printf(MSG_WARNING,
 			   "WPA: PTK0 rekey not allowed, disconnect " MACSTR,
 			   MAC2STR(sm->addr));
@@ -764,6 +765,8 @@ static void wpa_request_new_ptk(struct wpa_state_machine *sm)
 		/* Try to encourage the STA reconnect */
 		sm->disconnect_reason = WLAN_REASON_CLASS3_FRAME_FROM_NONASSOC_STA;
 	} else {
+		if (sm->use_extended_key_id)
+			sm->keyidx_active ^= 1; /* flip keyID */
 		sm->PTKRequest = TRUE;
 		sm->PTK_valid = 0;
 	}
@@ -1727,6 +1730,11 @@ void wpa_remove_ptk(struct wpa_state_machine *sm)
 			     0, KEY_FLAG_PAIRWISE))
 		wpa_printf(MSG_DEBUG,
 			   "RSN: PTK removal from the driver failed");
+	if (sm->wpa_auth->conf.wpa_extended_key_id &&
+	    wpa_auth_set_key(sm->wpa_auth, 0, WPA_ALG_NONE, sm->addr, 1, NULL,
+			     0, KEY_FLAG_PAIRWISE))
+		wpa_printf(MSG_DEBUG,
+			   "RSN: PTK ID1 removal from the driver failed");
 	sm->pairwise_set = FALSE;
 	eloop_cancel_timeout(wpa_rekey_ptk, sm->wpa_auth, sm);
 }
@@ -1786,7 +1794,8 @@ int wpa_auth_sm_event(struct wpa_state_machine *sm, enum wpa_event event)
 			sm->AuthenticationRequest = TRUE;
 			break;
 		} else {
-			if (sm->wpa_auth->conf.wpa_deny_ptk0_rekey) {
+			if (!sm->use_extended_key_id &&
+			    sm->wpa_auth->conf.wpa_deny_ptk0_rekey) {
 				wpa_printf(MSG_WARNING,
 					   "WPA: PTK0 rekey not allowed, disconnect "
 					   MACSTR, MAC2STR(sm->addr));
@@ -1795,6 +1804,9 @@ int wpa_auth_sm_event(struct wpa_state_machine *sm, enum wpa_event event)
 				sm->disconnect_reason = WLAN_REASON_CLASS3_FRAME_FROM_NONASSOC_STA;
 				break;
 			}
+
+			if (sm->use_extended_key_id)
+				sm->keyidx_active ^= 1; /* flip keyID */
 		}
 		if (sm->GUpdateStationKeys) {
 			/*
@@ -3160,12 +3172,12 @@ static int ocv_oci_add(struct wpa_state_machine *sm, u8 **argpos)
 
 SM_STATE(WPA_PTK, PTKINITNEGOTIATING)
 {
-	u8 rsc[WPA_KEY_RSC_LEN], *_rsc, *gtk, *kde = NULL, *pos, dummy_gtk[32];
+	u8 rsc[WPA_KEY_RSC_LEN], *_rsc, *gtk, *pos, dummy_gtk[32], hdr[2];
 	size_t gtk_len, kde_len;
 	struct wpa_group *gsm = sm->group;
 	u8 *wpa_ie;
 	int wpa_ie_len, secure, gtkidx, encr = 0;
-	u8 *wpa_ie_buf = NULL;
+	u8 *wpa_ie_buf = NULL, *kde = NULL;
 
 	SM_ENTRY_MA(WPA_PTK, PTKINITNEGOTIATING, wpa_ptk);
 	sm->TimeoutEvt = FALSE;
@@ -3231,6 +3243,18 @@ SM_STATE(WPA_PTK, PTKINITNEGOTIATING)
 	wpa_auth_logger(sm->wpa_auth, sm->addr, LOGGER_DEBUG,
 			"sending 3/4 msg of 4-Way Handshake");
 	if (sm->wpa == WPA_VERSION_WPA2) {
+		if (sm->use_extended_key_id && sm->TimeoutCtr == 1 &&
+		    wpa_auth_set_key(sm->wpa_auth, 0,
+				     wpa_cipher_to_alg(sm->pairwise),
+				     sm->addr,
+				     sm->keyidx_active, sm->PTK.tk,
+				     wpa_cipher_key_len(sm->pairwise),
+				     KEY_FLAG_PAIRWISE_RX)) {
+			wpa_sta_disconnect(sm->wpa_auth, sm->addr,
+					   WLAN_REASON_PREV_AUTH_NOT_VALID);
+			return;
+		}
+
 		/* WPA2 send GTK in the 4-way handshake */
 		secure = 1;
 		gtk = gsm->GTK[gsm->GN - 1];
@@ -3271,6 +3295,10 @@ SM_STATE(WPA_PTK, PTKINITNEGOTIATING)
 	}
 
 	kde_len = wpa_ie_len + ieee80211w_kde_len(sm) + ocv_oci_len(sm);
+
+	if (sm->use_extended_key_id)
+		kde_len += 2 + RSN_SELECTOR_LEN + 2;
+
 	if (gtk)
 		kde_len += 2 + RSN_SELECTOR_LEN + 2 + gtk_len;
 #ifdef CONFIG_IEEE80211R_AP
@@ -3306,10 +3334,15 @@ SM_STATE(WPA_PTK, PTKINITNEGOTIATING)
 		pos += elen;
 	}
 #endif /* CONFIG_IEEE80211R_AP */
+	hdr[1] = 0;
+
+	if (sm->use_extended_key_id) {
+		hdr[0] = sm->keyidx_active & 0x01;
+		pos = wpa_add_kde(pos, RSN_KEY_DATA_KEYID, hdr, 2, NULL, 0);
+	}
+
 	if (gtk) {
-		u8 hdr[2];
 		hdr[0] = gtkidx & 0x03;
-		hdr[1] = 0;
 		pos = wpa_add_kde(pos, RSN_KEY_DATA_GROUPKEY, hdr, 2,
 				  gtk, gtk_len);
 	}
@@ -3391,9 +3424,17 @@ SM_STATE(WPA_PTK, PTKINITDONE)
 	if (sm->Pair) {
 		enum wpa_alg alg = wpa_cipher_to_alg(sm->pairwise);
 		int klen = wpa_cipher_key_len(sm->pairwise);
-		if (wpa_auth_set_key(sm->wpa_auth, 0, alg, sm->addr, 0,
-				     sm->PTK.tk, klen,
-				     KEY_FLAG_PAIRWISE_RX_TX)) {
+		if (sm->use_extended_key_id) {
+			if (wpa_auth_set_key(sm->wpa_auth, 0, 0, sm->addr,
+					     sm->keyidx_active, NULL, 0,
+					     KEY_FLAG_PAIRWISE_RX_TX_MODIFY)) {
+				wpa_sta_disconnect(sm->wpa_auth, sm->addr,
+						   WLAN_REASON_PREV_AUTH_NOT_VALID);
+				return;
+			}
+		} else if (wpa_auth_set_key(sm->wpa_auth, 0, alg, sm->addr, 0,
+					    sm->PTK.tk, klen,
+					    KEY_FLAG_PAIRWISE_RX_TX)) {
 			wpa_sta_disconnect(sm->wpa_auth, sm->addr,
 					   WLAN_REASON_PREV_AUTH_NOT_VALID);
 			return;
@@ -5016,7 +5057,7 @@ int wpa_auth_resend_m3(struct wpa_state_machine *sm,
 		       void (*cb)(void *ctx1, void *ctx2),
 		       void *ctx1, void *ctx2)
 {
-	u8 rsc[WPA_KEY_RSC_LEN], *_rsc, *gtk, *kde, *pos;
+	u8 rsc[WPA_KEY_RSC_LEN], *_rsc, *gtk, *kde, *pos, hdr[2];
 	u8 *opos;
 	size_t gtk_len, kde_len;
 	struct wpa_group *gsm = sm->group;
@@ -5074,6 +5115,10 @@ int wpa_auth_resend_m3(struct wpa_state_machine *sm,
 	}
 
 	kde_len = wpa_ie_len + ieee80211w_kde_len(sm) + ocv_oci_len(sm);
+
+	if (sm->use_extended_key_id)
+		kde_len += 2 + RSN_SELECTOR_LEN + 2;
+
 	if (gtk)
 		kde_len += 2 + RSN_SELECTOR_LEN + 2 + gtk_len;
 #ifdef CONFIG_IEEE80211R_AP
@@ -5106,10 +5151,15 @@ int wpa_auth_resend_m3(struct wpa_state_machine *sm,
 		pos += elen;
 	}
 #endif /* CONFIG_IEEE80211R_AP */
+	hdr[1] = 0;
+
+	if (sm->use_extended_key_id) {
+		hdr[0] = sm->keyidx_active & 0x01;
+		pos = wpa_add_kde(pos, RSN_KEY_DATA_KEYID, hdr, 2, NULL, 0);
+	}
+
 	if (gtk) {
-		u8 hdr[2];
 		hdr[0] = gtkidx & 0x03;
-		hdr[1] = 0;
 		pos = wpa_add_kde(pos, RSN_KEY_DATA_GROUPKEY, hdr, 2,
 				  gtk, gtk_len);
 	}
diff --git a/src/ap/wpa_auth.h b/src/ap/wpa_auth.h
index 64c261964..332dff30c 100644
--- a/src/ap/wpa_auth.h
+++ b/src/ap/wpa_auth.h
@@ -169,6 +169,7 @@ struct ft_remote_r1kh {
 
 struct wpa_auth_config {
 	int wpa;
+	int wpa_extended_key_id;
 	int wpa_key_mgmt;
 	int wpa_pairwise;
 	int wpa_group;
diff --git a/src/ap/wpa_auth_glue.c b/src/ap/wpa_auth_glue.c
index 7abb93e73..a918daf00 100644
--- a/src/ap/wpa_auth_glue.c
+++ b/src/ap/wpa_auth_glue.c
@@ -39,6 +39,7 @@ static void hostapd_wpa_auth_conf(struct hostapd_bss_config *conf,
 {
 	os_memset(wconf, 0, sizeof(*wconf));
 	wconf->wpa = conf->wpa;
+	wconf->wpa_extended_key_id = conf->wpa_extended_key_id;
 	wconf->wpa_key_mgmt = conf->wpa_key_mgmt;
 	wconf->wpa_pairwise = conf->wpa_pairwise;
 	wconf->wpa_group = conf->wpa_group;
@@ -374,7 +375,12 @@ static int hostapd_wpa_auth_set_key(void *ctx, int vlan_id, enum wpa_alg alg,
 	}
 
 #ifdef CONFIG_TESTING_OPTIONS
-	if (addr && !is_broadcast_ether_addr(addr)) {
+	if (key_flag == KEY_FLAG_PAIRWISE_RX_TX_MODIFY) {
+		/* KEY_FLAG_PAIRWISE_RX installed the key and updated the
+		 * variables. Doing it again for KEY_FLAG_PAIRWISE_RX_TX_MODIFY
+		 * would overwrite the desired information with zeros.
+		 */
+	} else if (addr && !is_broadcast_ether_addr(addr)) {
 		struct sta_info *sta;
 
 		sta = ap_get_sta(hapd, addr);
@@ -1360,6 +1366,20 @@ int hostapd_setup_wpa(struct hostapd_data *hapd)
 		_conf.wpa_deny_ptk0_rekey = 0;
 	}
 
+	if (_conf.wpa_extended_key_id) {
+		if (hapd->iface->drv_flags & WPA_DRIVER_FLAGS_EXTENDED_KEY_ID) {
+			wpa_msg(hapd->msg_ctx, MSG_INFO,
+				"Enable Extended Key ID support");
+		} else {
+			wpa_msg(hapd->msg_ctx, MSG_INFO,
+				"Extended Key ID not supported by driver");
+			_conf.wpa_extended_key_id = 0;
+		}
+	} else if (_conf.wpa & WPA_PROTO_RSN) {
+		wpa_msg(hapd->msg_ctx, MSG_INFO,
+			"Extended Key ID support disabled");
+	}
+
 	hapd->wpa_auth = wpa_init(hapd->own_addr, &_conf, &cb, hapd);
 	if (hapd->wpa_auth == NULL) {
 		wpa_printf(MSG_ERROR, "WPA initialization failed.");
diff --git a/src/ap/wpa_auth_i.h b/src/ap/wpa_auth_i.h
index a993f5008..0b182391b 100644
--- a/src/ap/wpa_auth_i.h
+++ b/src/ap/wpa_auth_i.h
@@ -61,6 +61,8 @@ struct wpa_state_machine {
 	unsigned int pmk_len;
 	u8 pmkid[PMKID_LEN]; /* valid if pmkid_set == 1 */
 	struct wpa_ptk PTK;
+	u8 keyidx_active;
+	Boolean use_extended_key_id;
 	Boolean PTK_valid;
 	Boolean pairwise_set;
 	Boolean tk_already_set;
@@ -285,6 +287,7 @@ int wpa_auth_for_each_sta(struct wpa_authenticator *wpa_auth,
 int wpa_auth_for_each_auth(struct wpa_authenticator *wpa_auth,
 			   int (*cb)(struct wpa_authenticator *a, void *ctx),
 			   void *cb_ctx);
+int handle_extended_key_id(struct wpa_state_machine *sm, int capabilities);
 
 #ifdef CONFIG_IEEE80211R_AP
 int wpa_write_mdie(struct wpa_auth_config *conf, u8 *buf, size_t len);
diff --git a/src/ap/wpa_auth_ie.c b/src/ap/wpa_auth_ie.c
index 2e6d05910..cb01e4d7f 100644
--- a/src/ap/wpa_auth_ie.c
+++ b/src/ap/wpa_auth_ie.c
@@ -284,6 +284,8 @@ int wpa_write_rsn_ie(struct wpa_auth_config *conf, u8 *buf, size_t len,
 		/* 4 PTKSA replay counters when using WMM */
 		capab |= (RSN_NUM_REPLAY_COUNTERS_16 << 2);
 	}
+	if (conf->wpa_extended_key_id)
+		capab |= WPA_CAPABILITY_EXT_KEY_ID_FOR_UNICAST;
 	if (conf->ieee80211w != NO_MGMT_FRAME_PROTECTION) {
 		capab |= WPA_CAPABILITY_MFPC;
 		if (conf->ieee80211w == MGMT_FRAME_PROTECTION_REQUIRED)
@@ -447,8 +449,9 @@ int wpa_auth_gen_wpa_ie(struct wpa_authenticator *wpa_auth)
 {
 	u8 *pos, buf[128];
 	int res;
-
 #ifdef CONFIG_TESTING_OPTIONS
+	struct wpa_ie_data data;
+
 	if (wpa_auth->conf.own_ie_override_len) {
 		wpa_hexdump(MSG_DEBUG, "WPA: Forced own IE(s) for testing",
 			    wpa_auth->conf.own_ie_override,
@@ -461,6 +464,14 @@ int wpa_auth_gen_wpa_ie(struct wpa_authenticator *wpa_auth)
 		os_memcpy(wpa_auth->wpa_ie, wpa_auth->conf.own_ie_override,
 			  wpa_auth->conf.own_ie_override_len);
 		wpa_auth->wpa_ie_len = wpa_auth->conf.own_ie_override_len;
+		if (wpa_parse_wpa_ie_rsn(wpa_auth->wpa_ie,
+					 wpa_auth->wpa_ie_len, &data) ||
+		    !(data.capabilities &
+		       WPA_CAPABILITY_EXT_KEY_ID_FOR_UNICAST)) {
+			wpa_printf(MSG_DEBUG,
+				   "WPA: Own IE forcing wpa_extended_key_id=0");
+			wpa_auth->conf.wpa_extended_key_id = 0;
+		}
 		return 0;
 	}
 #endif /* CONFIG_TESTING_OPTIONS */
@@ -545,6 +556,44 @@ static int wpa_auth_okc_iter(struct wpa_authenticator *a, void *ctx)
 	return 0;
 }
 
+int handle_extended_key_id(struct wpa_state_machine *sm, int capabilities)
+{
+	struct wpa_auth_config *conf = &sm->wpa_auth->conf;
+
+	if (conf->wpa_extended_key_id &&
+	    sm->pairwise != WPA_CIPHER_TKIP &&
+	    capabilities & WPA_CAPABILITY_EXT_KEY_ID_FOR_UNICAST) {
+		if (!sm->use_extended_key_id && sm->pairwise_set) {
+			wpa_printf(MSG_ERROR, "STA " MACSTR
+				   " tries to start using Extended Key ID on rekey",
+				   MAC2STR(sm->addr));
+			return -1;
+		} else if (!sm->use_extended_key_id) {
+			wpa_printf(MSG_DEBUG, "STA " MACSTR
+				   " supports Extended Key ID",
+				   MAC2STR(sm->addr));
+			sm->use_extended_key_id = TRUE;
+		} else if (!sm->pairwise_set) {
+			wpa_printf(MSG_DEBUG, "STA " MACSTR
+				   " is not supporting Extended Key ID",
+				   MAC2STR(sm->addr));
+		}
+	} else {
+		if (sm->use_extended_key_id && sm->pairwise_set) {
+			wpa_printf(MSG_ERROR, "STA " MACSTR
+				   " is using Extended Key ID, can't rekey without it",
+				   MAC2STR(sm->addr));
+			return -1;
+		} else if (!sm->pairwise_set) {
+			wpa_printf(MSG_DEBUG, "STA " MACSTR
+				   " can't use Extended Key ID",
+				   MAC2STR(sm->addr));
+			sm->use_extended_key_id = FALSE;
+			sm->keyidx_active = 0;
+		}
+	}
+	return 0;
+}
 
 int wpa_validate_wpa_ie(struct wpa_authenticator *wpa_auth,
 			struct wpa_state_machine *sm, int freq,
@@ -870,6 +919,9 @@ int wpa_validate_wpa_ie(struct wpa_authenticator *wpa_auth,
 	else
 		sm->wpa = WPA_VERSION_WPA;
 
+	if (handle_extended_key_id(sm, data.capabilities))
+		return WPA_INVALID_IE;
+
 #if defined(CONFIG_IEEE80211R_AP) && defined(CONFIG_FILS)
 	if ((sm->wpa_key_mgmt == WPA_KEY_MGMT_FT_FILS_SHA256 ||
 	     sm->wpa_key_mgmt == WPA_KEY_MGMT_FT_FILS_SHA384) &&
-- 
2.24.1


_______________________________________________
Hostap mailing list
Hostap@xxxxxxxxxxxxxxxxxxx
http://lists.infradead.org/mailman/listinfo/hostap



[Index of Archives]     [Linux Wireless]     [Linux Kernel]     [ATH6KL]     [Linux Bluetooth]     [Linux Netdev]     [Kernel Newbies]     [IDE]     [Security]     [Git]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux ATA RAID]     [Samba]     [Device Mapper]

  Powered by Linux