mirror of
https://github.com/vanhoefm/fragattacks.git
synced 2024-11-28 18:28:23 -05:00
VLAN: Separate station grouping and uplink configuration
Separate uplink configuration (IEEE 802.1q VID) and grouping of stations into AP_VLAN interfaces. The int vlan_id will continue to identify the AP_VLAN interface the station should be assigned to. Each AP_VLAN interface corresponds to an instance of struct hostapd_vlan that is uniquely identified by int vlan_id within an BSS. New: Each station and struct hostapd_vlan holds a struct vlan_description vlan_desc member that describes the uplink configuration requested. Currently this is just an int untagged IEEE 802.1q VID, but can be extended to tagged VLANs and other settings easily. When the station was about to be assigned its vlan_id, vlan_desc and vlan_id will now be set simultaneously by ap_sta_set_vlan(). So sta->vlan_id can still be tested for whether the station needs to be moved to an AP_VLAN interface. To ease addition of tagged VLAN support, a member notempty is added to struct vlan_description. Is is set to 1 if an untagged or tagged VLAN assignment is requested and needs to be validated. The inverted form allows os_zalloc() to initialize an empty description. Though not depended on by the code, vlan_id assignment ensures: * vlan_id = 0 will continue to mean no AP_VLAN interface * vlan_id < 4096 will continue to mean vlan_id = untagged vlan id with no per_sta_vif and no extra tagged vlan. * vlan_id > 4096 will be used for per_sta_vif and/or tagged vlans. This way struct wpa_group and drivers API do not need to be changed in order to implement tagged VLANs or per_sta_vif support. DYNAMIC_VLAN_* will refer to (struct vlan_description).notempty only, thus grouping of the stations for per_sta_vif can be used with DYNAMIC_VLAN_DISABLED, but not with CONFIG_NO_VLAN, as struct hostapd_vlan is still used to manage AP_VLAN interfaces. MAX_VLAN_ID will be checked in hostapd_vlan_valid and during setup of VLAN interfaces and refer to IEEE 802.1q VID. VLAN_ID_WILDCARD will continue to refer to int vlan_id. Renaming vlan_id to vlan_desc when type changed from int to struct vlan_description was avoided when vlan_id was also used in a way that did not depend on its type (for example, when passed to another function). Output of "VLAN ID %d" continues to refer to int vlan_id, while "VLAN %d" will refer to untagged IEEE 802.1q VID. Signed-off-by: Michael Braun <michael-dev@fami-braun.de>
This commit is contained in:
parent
3a583e0023
commit
1889af2e0f
@ -176,6 +176,7 @@ ifdef CONFIG_NO_VLAN
|
||||
L_CFLAGS += -DCONFIG_NO_VLAN
|
||||
else
|
||||
OBJS += src/ap/vlan_init.c
|
||||
OBJS += src/ap/vlan.c
|
||||
ifdef CONFIG_VLAN_NETLINK
|
||||
ifdef CONFIG_FULL_DYNAMIC_VLAN
|
||||
OBJS += src/ap/vlan_util.c
|
||||
|
@ -194,6 +194,7 @@ ifdef CONFIG_NO_VLAN
|
||||
CFLAGS += -DCONFIG_NO_VLAN
|
||||
else
|
||||
OBJS += ../src/ap/vlan_init.o
|
||||
OBJS += ../src/ap/vlan.o
|
||||
ifdef CONFIG_VLAN_NETLINK
|
||||
ifdef CONFIG_FULL_DYNAMIC_VLAN
|
||||
OBJS += ../src/ap/vlan_util.o
|
||||
|
@ -97,6 +97,8 @@ static int hostapd_config_read_vlan_file(struct hostapd_bss_config *bss,
|
||||
}
|
||||
|
||||
vlan->vlan_id = vlan_id;
|
||||
vlan->vlan_desc.untagged = vlan_id;
|
||||
vlan->vlan_desc.notempty = !!vlan_id;
|
||||
os_strlcpy(vlan->ifname, pos, sizeof(vlan->ifname));
|
||||
vlan->next = bss->vlan;
|
||||
bss->vlan = vlan;
|
||||
@ -197,7 +199,10 @@ static int hostapd_config_read_maclist(const char *fname,
|
||||
|
||||
*acl = newacl;
|
||||
os_memcpy((*acl)[*num].addr, addr, ETH_ALEN);
|
||||
(*acl)[*num].vlan_id = vlan_id;
|
||||
os_memset(&(*acl)[*num].vlan_id, 0,
|
||||
sizeof((*acl)[*num].vlan_id));
|
||||
(*acl)[*num].vlan_id.untagged = vlan_id;
|
||||
(*acl)[*num].vlan_id.notempty = !!vlan_id;
|
||||
(*num)++;
|
||||
}
|
||||
|
||||
|
@ -1322,7 +1322,7 @@ static int hostapd_ctrl_iface_set(struct hostapd_data *hapd, char *cmd)
|
||||
#endif /* CONFIG_TESTING_OPTIONS */
|
||||
} else {
|
||||
struct sta_info *sta;
|
||||
int vlan_id;
|
||||
struct vlan_description vlan_id;
|
||||
|
||||
ret = hostapd_set_iface(hapd->iconf, hapd->conf, cmd, value);
|
||||
if (ret)
|
||||
@ -1334,7 +1334,8 @@ static int hostapd_ctrl_iface_set(struct hostapd_data *hapd, char *cmd)
|
||||
hapd->conf->deny_mac,
|
||||
hapd->conf->num_deny_mac, sta->addr,
|
||||
&vlan_id) &&
|
||||
(!vlan_id || vlan_id == sta->vlan_id))
|
||||
(!vlan_id.notempty ||
|
||||
!vlan_compare(&vlan_id, sta->vlan_desc)))
|
||||
ap_sta_disconnect(
|
||||
hapd, sta, sta->addr,
|
||||
WLAN_REASON_UNSPECIFIED);
|
||||
@ -1346,7 +1347,8 @@ static int hostapd_ctrl_iface_set(struct hostapd_data *hapd, char *cmd)
|
||||
hapd->conf->accept_mac,
|
||||
hapd->conf->num_accept_mac,
|
||||
sta->addr, &vlan_id) ||
|
||||
(vlan_id && vlan_id != sta->vlan_id))
|
||||
(vlan_id.notempty &&
|
||||
vlan_compare(&vlan_id, sta->vlan_desc)))
|
||||
ap_sta_disconnect(
|
||||
hapd, sta, sta->addr,
|
||||
WLAN_REASON_UNSPECIFIED);
|
||||
|
@ -629,7 +629,7 @@ void hostapd_config_free(struct hostapd_config *conf)
|
||||
* Perform a binary search for given MAC address from a pre-sorted list.
|
||||
*/
|
||||
int hostapd_maclist_found(struct mac_acl_entry *list, int num_entries,
|
||||
const u8 *addr, int *vlan_id)
|
||||
const u8 *addr, struct vlan_description *vlan_id)
|
||||
{
|
||||
int start, end, middle, res;
|
||||
|
||||
@ -669,11 +669,18 @@ int hostapd_rate_found(int *list, int rate)
|
||||
}
|
||||
|
||||
|
||||
int hostapd_vlan_id_valid(struct hostapd_vlan *vlan, int vlan_id)
|
||||
int hostapd_vlan_valid(struct hostapd_vlan *vlan,
|
||||
struct vlan_description *vlan_desc)
|
||||
{
|
||||
struct hostapd_vlan *v = vlan;
|
||||
|
||||
if (!vlan_desc->notempty || vlan_desc->untagged <= 0 ||
|
||||
vlan_desc->untagged > MAX_VLAN_ID)
|
||||
return 0;
|
||||
|
||||
while (v) {
|
||||
if (v->vlan_id == vlan_id || v->vlan_id == VLAN_ID_WILDCARD)
|
||||
if (!vlan_compare(&v->vlan_desc, vlan_desc) ||
|
||||
v->vlan_id == VLAN_ID_WILDCARD)
|
||||
return 1;
|
||||
v = v->next;
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include "common/ieee802_11_common.h"
|
||||
#include "wps/wps.h"
|
||||
#include "fst/fst.h"
|
||||
#include "vlan.h"
|
||||
|
||||
/**
|
||||
* mesh_conf - local MBSS state and settings
|
||||
@ -53,7 +54,7 @@ typedef u8 macaddr[ETH_ALEN];
|
||||
|
||||
struct mac_acl_entry {
|
||||
macaddr addr;
|
||||
int vlan_id;
|
||||
struct vlan_description vlan_id;
|
||||
};
|
||||
|
||||
struct hostapd_radius_servers;
|
||||
@ -114,6 +115,7 @@ struct hostapd_ssid {
|
||||
struct hostapd_vlan {
|
||||
struct hostapd_vlan *next;
|
||||
int vlan_id; /* VLAN ID or -1 (VLAN_ID_WILDCARD) for wildcard entry */
|
||||
struct vlan_description vlan_desc;
|
||||
char ifname[IFNAMSIZ + 1];
|
||||
int configured;
|
||||
int dynamic_vlan;
|
||||
@ -690,13 +692,14 @@ void hostapd_config_clear_wpa_psk(struct hostapd_wpa_psk **p);
|
||||
void hostapd_config_free_bss(struct hostapd_bss_config *conf);
|
||||
void hostapd_config_free(struct hostapd_config *conf);
|
||||
int hostapd_maclist_found(struct mac_acl_entry *list, int num_entries,
|
||||
const u8 *addr, int *vlan_id);
|
||||
const u8 *addr, struct vlan_description *vlan_id);
|
||||
int hostapd_rate_found(int *list, int rate);
|
||||
const u8 * hostapd_get_psk(const struct hostapd_bss_config *conf,
|
||||
const u8 *addr, const u8 *p2p_dev_addr,
|
||||
const u8 *prev_psk);
|
||||
int hostapd_setup_wpa_psk(struct hostapd_bss_config *conf);
|
||||
int hostapd_vlan_id_valid(struct hostapd_vlan *vlan, int vlan_id);
|
||||
int hostapd_vlan_valid(struct hostapd_vlan *vlan,
|
||||
struct vlan_description *vlan_desc);
|
||||
const char * hostapd_get_vlan_id_ifname(struct hostapd_vlan *vlan,
|
||||
int vlan_id);
|
||||
struct hostapd_radius_attr *
|
||||
|
@ -886,7 +886,7 @@ static void handle_auth(struct hostapd_data *hapd,
|
||||
u16 fc;
|
||||
const u8 *challenge = NULL;
|
||||
u32 session_timeout, acct_interim_interval;
|
||||
int vlan_id = 0;
|
||||
struct vlan_description vlan_id;
|
||||
struct hostapd_sta_wpa_psk_short *psk = NULL;
|
||||
u8 resp_ies[2 + WLAN_AUTH_CHALLENGE_LEN];
|
||||
size_t resp_ies_len = 0;
|
||||
@ -894,6 +894,8 @@ static void handle_auth(struct hostapd_data *hapd,
|
||||
char *radius_cui = NULL;
|
||||
u16 seq_ctrl;
|
||||
|
||||
os_memset(&vlan_id, 0, sizeof(vlan_id));
|
||||
|
||||
if (len < IEEE80211_HDRLEN + sizeof(mgmt->u.auth)) {
|
||||
wpa_printf(MSG_INFO, "handle_auth - too short payload (len=%lu)",
|
||||
(unsigned long) len);
|
||||
@ -1095,16 +1097,19 @@ static void handle_auth(struct hostapd_data *hapd,
|
||||
sta->last_seq_ctrl = seq_ctrl;
|
||||
sta->last_subtype = WLAN_FC_STYPE_AUTH;
|
||||
|
||||
if (vlan_id > 0) {
|
||||
if (!hostapd_vlan_id_valid(hapd->conf->vlan, vlan_id)) {
|
||||
if (vlan_id.notempty) {
|
||||
if (!hostapd_vlan_valid(hapd->conf->vlan, &vlan_id)) {
|
||||
hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_RADIUS,
|
||||
HOSTAPD_LEVEL_INFO, "Invalid VLAN ID "
|
||||
"%d received from RADIUS server",
|
||||
vlan_id);
|
||||
HOSTAPD_LEVEL_INFO,
|
||||
"Invalid VLAN %d received from RADIUS server",
|
||||
vlan_id.untagged);
|
||||
resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
|
||||
goto fail;
|
||||
}
|
||||
if (ap_sta_set_vlan(hapd, sta, &vlan_id) < 0) {
|
||||
resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
|
||||
goto fail;
|
||||
}
|
||||
sta->vlan_id = vlan_id;
|
||||
hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_RADIUS,
|
||||
HOSTAPD_LEVEL_INFO, "VLAN ID %d", sta->vlan_id);
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ struct hostapd_cached_radius_acl {
|
||||
struct hostapd_cached_radius_acl *next;
|
||||
u32 session_timeout;
|
||||
u32 acct_interim_interval;
|
||||
int vlan_id;
|
||||
struct vlan_description vlan_id;
|
||||
struct hostapd_sta_wpa_psk_short *psk;
|
||||
char *identity;
|
||||
char *radius_cui;
|
||||
@ -99,7 +99,8 @@ static void copy_psk_list(struct hostapd_sta_wpa_psk_short **psk,
|
||||
|
||||
static int hostapd_acl_cache_get(struct hostapd_data *hapd, const u8 *addr,
|
||||
u32 *session_timeout,
|
||||
u32 *acct_interim_interval, int *vlan_id,
|
||||
u32 *acct_interim_interval,
|
||||
struct vlan_description *vlan_id,
|
||||
struct hostapd_sta_wpa_psk_short **psk,
|
||||
char **identity, char **radius_cui)
|
||||
{
|
||||
@ -222,7 +223,8 @@ static int hostapd_radius_acl_query(struct hostapd_data *hapd, const u8 *addr,
|
||||
* @vlan_id: Buffer for returning VLAN ID
|
||||
* Returns: HOSTAPD_ACL_ACCEPT, HOSTAPD_ACL_REJECT, or HOSTAPD_ACL_PENDING
|
||||
*/
|
||||
int hostapd_check_acl(struct hostapd_data *hapd, const u8 *addr, int *vlan_id)
|
||||
int hostapd_check_acl(struct hostapd_data *hapd, const u8 *addr,
|
||||
struct vlan_description *vlan_id)
|
||||
{
|
||||
if (hostapd_maclist_found(hapd->conf->accept_mac,
|
||||
hapd->conf->num_accept_mac, addr, vlan_id))
|
||||
@ -260,7 +262,8 @@ static int hostapd_radius_acl_query(struct hostapd_data *hapd, const u8 *addr,
|
||||
*/
|
||||
int hostapd_allowed_address(struct hostapd_data *hapd, const u8 *addr,
|
||||
const u8 *msg, size_t len, u32 *session_timeout,
|
||||
u32 *acct_interim_interval, int *vlan_id,
|
||||
u32 *acct_interim_interval,
|
||||
struct vlan_description *vlan_id,
|
||||
struct hostapd_sta_wpa_psk_short **psk,
|
||||
char **identity, char **radius_cui)
|
||||
{
|
||||
@ -271,7 +274,7 @@ int hostapd_allowed_address(struct hostapd_data *hapd, const u8 *addr,
|
||||
if (acct_interim_interval)
|
||||
*acct_interim_interval = 0;
|
||||
if (vlan_id)
|
||||
*vlan_id = 0;
|
||||
os_memset(vlan_id, 0, sizeof(*vlan_id));
|
||||
if (psk)
|
||||
*psk = NULL;
|
||||
if (identity)
|
||||
@ -556,7 +559,8 @@ hostapd_acl_recv_radius(struct radius_msg *msg, struct radius_msg *req,
|
||||
cache->acct_interim_interval = 0;
|
||||
}
|
||||
|
||||
cache->vlan_id = radius_msg_get_vlanid(msg);
|
||||
cache->vlan_id.untagged = radius_msg_get_vlanid(msg);
|
||||
cache->vlan_id.notempty = !!cache->vlan_id.untagged;
|
||||
|
||||
decode_tunnel_passwords(hapd, shared_secret, shared_secret_len,
|
||||
msg, req, cache);
|
||||
@ -579,17 +583,17 @@ hostapd_acl_recv_radius(struct radius_msg *msg, struct radius_msg *req,
|
||||
!cache->psk)
|
||||
cache->accepted = HOSTAPD_ACL_REJECT;
|
||||
|
||||
if (cache->vlan_id &&
|
||||
!hostapd_vlan_id_valid(hapd->conf->vlan, cache->vlan_id)) {
|
||||
if (cache->vlan_id.notempty &&
|
||||
!hostapd_vlan_valid(hapd->conf->vlan, &cache->vlan_id)) {
|
||||
hostapd_logger(hapd, query->addr,
|
||||
HOSTAPD_MODULE_RADIUS,
|
||||
HOSTAPD_LEVEL_INFO,
|
||||
"Invalid VLAN ID %d received from RADIUS server",
|
||||
cache->vlan_id);
|
||||
cache->vlan_id = 0;
|
||||
"Invalid VLAN %d received from RADIUS server",
|
||||
cache->vlan_id.untagged);
|
||||
os_memset(&cache->vlan_id, 0, sizeof(cache->vlan_id));
|
||||
}
|
||||
if (hapd->conf->ssid.dynamic_vlan == DYNAMIC_VLAN_REQUIRED &&
|
||||
!cache->vlan_id)
|
||||
!cache->vlan_id.notempty)
|
||||
cache->accepted = HOSTAPD_ACL_REJECT;
|
||||
} else
|
||||
cache->accepted = HOSTAPD_ACL_REJECT;
|
||||
|
@ -16,10 +16,12 @@ enum {
|
||||
HOSTAPD_ACL_ACCEPT_TIMEOUT = 3
|
||||
};
|
||||
|
||||
int hostapd_check_acl(struct hostapd_data *hapd, const u8 *addr, int *vlan_id);
|
||||
int hostapd_check_acl(struct hostapd_data *hapd, const u8 *addr,
|
||||
struct vlan_description *vlan_id);
|
||||
int hostapd_allowed_address(struct hostapd_data *hapd, const u8 *addr,
|
||||
const u8 *msg, size_t len, u32 *session_timeout,
|
||||
u32 *acct_interim_interval, int *vlan_id,
|
||||
u32 *acct_interim_interval,
|
||||
struct vlan_description *vlan_id,
|
||||
struct hostapd_sta_wpa_psk_short **psk,
|
||||
char **identity, char **radius_cui);
|
||||
int hostapd_acl_init(struct hostapd_data *hapd);
|
||||
|
@ -222,7 +222,7 @@ static void ieee802_1x_tx_key(struct hostapd_data *hapd, struct sta_info *sta)
|
||||
MAC2STR(sta->addr));
|
||||
|
||||
#ifndef CONFIG_NO_VLAN
|
||||
if (sta->vlan_id > 0 && sta->vlan_id <= MAX_VLAN_ID) {
|
||||
if (sta->vlan_id > 0) {
|
||||
wpa_printf(MSG_ERROR, "Using WEP with vlans is not supported.");
|
||||
return;
|
||||
}
|
||||
@ -1151,7 +1151,7 @@ void ieee802_1x_new_station(struct hostapd_data *hapd, struct sta_info *sta)
|
||||
sta->eapol_sm->authFail = FALSE;
|
||||
if (sta->eapol_sm->eap)
|
||||
eap_sm_notify_cached(sta->eapol_sm->eap);
|
||||
pmksa_cache_to_eapol_data(pmksa, sta->eapol_sm);
|
||||
pmksa_cache_to_eapol_data(hapd, pmksa, sta->eapol_sm);
|
||||
ap_sta_bind_vlan(hapd, sta);
|
||||
} else {
|
||||
if (reassoc) {
|
||||
@ -1617,10 +1617,13 @@ ieee802_1x_receive_auth(struct radius_msg *msg, struct radius_msg *req,
|
||||
struct hostapd_data *hapd = data;
|
||||
struct sta_info *sta;
|
||||
u32 session_timeout = 0, termination_action, acct_interim_interval;
|
||||
int session_timeout_set, vlan_id = 0;
|
||||
int session_timeout_set;
|
||||
struct eapol_state_machine *sm;
|
||||
int override_eapReq = 0;
|
||||
struct radius_hdr *hdr = radius_msg_get_hdr(msg);
|
||||
struct vlan_description vlan_desc;
|
||||
|
||||
os_memset(&vlan_desc, 0, sizeof(vlan_desc));
|
||||
|
||||
sm = ieee802_1x_search_radius_identifier(hapd, hdr->identifier);
|
||||
if (sm == NULL) {
|
||||
@ -1684,27 +1687,27 @@ ieee802_1x_receive_auth(struct radius_msg *msg, struct radius_msg *req,
|
||||
|
||||
switch (hdr->code) {
|
||||
case RADIUS_CODE_ACCESS_ACCEPT:
|
||||
if (hapd->conf->ssid.dynamic_vlan == DYNAMIC_VLAN_DISABLED)
|
||||
vlan_id = 0;
|
||||
#ifndef CONFIG_NO_VLAN
|
||||
else
|
||||
vlan_id = radius_msg_get_vlanid(msg);
|
||||
if (vlan_id > 0 &&
|
||||
hostapd_vlan_id_valid(hapd->conf->vlan, vlan_id)) {
|
||||
hostapd_logger(hapd, sta->addr,
|
||||
HOSTAPD_MODULE_RADIUS,
|
||||
HOSTAPD_LEVEL_INFO,
|
||||
"VLAN ID %d", vlan_id);
|
||||
} else if (vlan_id > 0) {
|
||||
if (hapd->conf->ssid.dynamic_vlan != DYNAMIC_VLAN_DISABLED) {
|
||||
vlan_desc.untagged = radius_msg_get_vlanid(msg);
|
||||
vlan_desc.notempty = !!vlan_desc.untagged;
|
||||
}
|
||||
|
||||
if (vlan_desc.notempty &&
|
||||
!hostapd_vlan_valid(hapd->conf->vlan, &vlan_desc)) {
|
||||
sta->eapol_sm->authFail = TRUE;
|
||||
hostapd_logger(hapd, sta->addr,
|
||||
HOSTAPD_MODULE_RADIUS,
|
||||
HOSTAPD_LEVEL_INFO,
|
||||
"Invalid VLAN ID %d received from RADIUS server",
|
||||
vlan_id);
|
||||
"Invalid VLAN %d received from RADIUS server",
|
||||
vlan_desc.untagged);
|
||||
os_memset(&vlan_desc, 0, sizeof(vlan_desc));
|
||||
ap_sta_set_vlan(hapd, sta, &vlan_desc);
|
||||
break;
|
||||
} else if (hapd->conf->ssid.dynamic_vlan ==
|
||||
DYNAMIC_VLAN_REQUIRED) {
|
||||
}
|
||||
|
||||
if (hapd->conf->ssid.dynamic_vlan == DYNAMIC_VLAN_REQUIRED &&
|
||||
!vlan_desc.notempty) {
|
||||
sta->eapol_sm->authFail = TRUE;
|
||||
hostapd_logger(hapd, sta->addr,
|
||||
HOSTAPD_MODULE_IEEE8021X,
|
||||
@ -1715,7 +1718,18 @@ ieee802_1x_receive_auth(struct radius_msg *msg, struct radius_msg *req,
|
||||
}
|
||||
#endif /* CONFIG_NO_VLAN */
|
||||
|
||||
sta->vlan_id = vlan_id;
|
||||
if (ap_sta_set_vlan(hapd, sta, &vlan_desc) < 0)
|
||||
break;
|
||||
|
||||
#ifndef CONFIG_NO_VLAN
|
||||
if (sta->vlan_id > 0) {
|
||||
hostapd_logger(hapd, sta->addr,
|
||||
HOSTAPD_MODULE_RADIUS,
|
||||
HOSTAPD_LEVEL_INFO,
|
||||
"VLAN ID %d", sta->vlan_id);
|
||||
}
|
||||
#endif /* CONFIG_NO_VLAN */
|
||||
|
||||
if ((sta->flags & WLAN_STA_ASSOC) &&
|
||||
ap_sta_bind_vlan(hapd, sta) < 0)
|
||||
break;
|
||||
|
@ -38,6 +38,7 @@ static void pmksa_cache_set_expiration(struct rsn_pmksa_cache *pmksa);
|
||||
|
||||
static void _pmksa_cache_free_entry(struct rsn_pmksa_cache_entry *entry)
|
||||
{
|
||||
os_free(entry->vlan_desc);
|
||||
os_free(entry->identity);
|
||||
wpabuf_free(entry->cui);
|
||||
#ifndef CONFIG_NO_RADIUS
|
||||
@ -126,6 +127,8 @@ static void pmksa_cache_set_expiration(struct rsn_pmksa_cache *pmksa)
|
||||
static void pmksa_cache_from_eapol_data(struct rsn_pmksa_cache_entry *entry,
|
||||
struct eapol_state_machine *eapol)
|
||||
{
|
||||
struct vlan_description *vlan_desc;
|
||||
|
||||
if (eapol == NULL)
|
||||
return;
|
||||
|
||||
@ -146,15 +149,26 @@ static void pmksa_cache_from_eapol_data(struct rsn_pmksa_cache_entry *entry,
|
||||
#endif /* CONFIG_NO_RADIUS */
|
||||
|
||||
entry->eap_type_authsrv = eapol->eap_type_authsrv;
|
||||
entry->vlan_id = ((struct sta_info *) eapol->sta)->vlan_id;
|
||||
|
||||
vlan_desc = ((struct sta_info *) eapol->sta)->vlan_desc;
|
||||
if (vlan_desc && vlan_desc->notempty) {
|
||||
entry->vlan_desc = os_zalloc(sizeof(struct vlan_description));
|
||||
if (entry->vlan_desc)
|
||||
*entry->vlan_desc = *vlan_desc;
|
||||
} else {
|
||||
entry->vlan_desc = NULL;
|
||||
}
|
||||
|
||||
entry->acct_multi_session_id = eapol->acct_multi_session_id;
|
||||
}
|
||||
|
||||
|
||||
void pmksa_cache_to_eapol_data(struct rsn_pmksa_cache_entry *entry,
|
||||
void pmksa_cache_to_eapol_data(struct hostapd_data *hapd,
|
||||
struct rsn_pmksa_cache_entry *entry,
|
||||
struct eapol_state_machine *eapol)
|
||||
{
|
||||
struct sta_info *sta;
|
||||
|
||||
if (entry == NULL || eapol == NULL)
|
||||
return;
|
||||
|
||||
@ -185,7 +199,8 @@ void pmksa_cache_to_eapol_data(struct rsn_pmksa_cache_entry *entry,
|
||||
}
|
||||
|
||||
eapol->eap_type_authsrv = entry->eap_type_authsrv;
|
||||
((struct sta_info *) eapol->sta)->vlan_id = entry->vlan_id;
|
||||
sta = (struct sta_info *) eapol->sta;
|
||||
ap_sta_set_vlan(hapd, sta, entry->vlan_desc);
|
||||
|
||||
eapol->acct_multi_session_id = entry->acct_multi_session_id;
|
||||
}
|
||||
@ -335,7 +350,13 @@ pmksa_cache_add_okc(struct rsn_pmksa_cache *pmksa,
|
||||
radius_copy_class(&entry->radius_class, &old_entry->radius_class);
|
||||
#endif /* CONFIG_NO_RADIUS */
|
||||
entry->eap_type_authsrv = old_entry->eap_type_authsrv;
|
||||
entry->vlan_id = old_entry->vlan_id;
|
||||
if (old_entry->vlan_desc) {
|
||||
entry->vlan_desc = os_zalloc(sizeof(struct vlan_description));
|
||||
if (entry->vlan_desc)
|
||||
*entry->vlan_desc = *old_entry->vlan_desc;
|
||||
} else {
|
||||
entry->vlan_desc = NULL;
|
||||
}
|
||||
entry->opportunistic = 1;
|
||||
|
||||
pmksa_cache_link_entry(pmksa, entry);
|
||||
|
@ -28,7 +28,7 @@ struct rsn_pmksa_cache_entry {
|
||||
struct wpabuf *cui;
|
||||
struct radius_class_data radius_class;
|
||||
u8 eap_type_authsrv;
|
||||
int vlan_id;
|
||||
struct vlan_description *vlan_desc;
|
||||
int opportunistic;
|
||||
|
||||
u64 acct_multi_session_id;
|
||||
@ -56,7 +56,8 @@ struct rsn_pmksa_cache_entry *
|
||||
pmksa_cache_add_okc(struct rsn_pmksa_cache *pmksa,
|
||||
const struct rsn_pmksa_cache_entry *old_entry,
|
||||
const u8 *aa, const u8 *pmkid);
|
||||
void pmksa_cache_to_eapol_data(struct rsn_pmksa_cache_entry *entry,
|
||||
void pmksa_cache_to_eapol_data(struct hostapd_data *hapd,
|
||||
struct rsn_pmksa_cache_entry *entry,
|
||||
struct eapol_state_machine *eapol);
|
||||
void pmksa_cache_free_entry(struct rsn_pmksa_cache *pmksa,
|
||||
struct rsn_pmksa_cache_entry *entry);
|
||||
|
@ -34,6 +34,7 @@
|
||||
#include "wnm_ap.h"
|
||||
#include "ndisc_snoop.h"
|
||||
#include "sta_info.h"
|
||||
#include "vlan.h"
|
||||
|
||||
static void ap_sta_remove_in_other_bss(struct hostapd_data *hapd,
|
||||
struct sta_info *sta);
|
||||
@ -266,6 +267,8 @@ void ap_free_sta(struct hostapd_data *hapd, struct sta_info *sta)
|
||||
* vlan_remove_dynamic() can check that no stations are left on the
|
||||
* AP_VLAN netdev.
|
||||
*/
|
||||
if (sta->vlan_id)
|
||||
vlan_remove_dynamic(hapd, sta->vlan_id);
|
||||
if (sta->vlan_id_bound) {
|
||||
/*
|
||||
* Need to remove the STA entry before potentially removing the
|
||||
@ -802,6 +805,78 @@ int ap_sta_wps_cancel(struct hostapd_data *hapd,
|
||||
#endif /* CONFIG_WPS */
|
||||
|
||||
|
||||
int ap_sta_set_vlan(struct hostapd_data *hapd, struct sta_info *sta,
|
||||
struct vlan_description *vlan_desc)
|
||||
{
|
||||
struct hostapd_vlan *vlan = NULL, *wildcard_vlan = NULL;
|
||||
int old_vlan_id, vlan_id = 0, ret = 0;
|
||||
|
||||
if (hapd->conf->ssid.dynamic_vlan == DYNAMIC_VLAN_DISABLED) {
|
||||
vlan_desc = NULL;
|
||||
} else if (vlan_desc && vlan_desc->notempty) {
|
||||
if (!vlan_compare(vlan_desc, sta->vlan_desc))
|
||||
return 0; /* nothing to change */
|
||||
|
||||
for (vlan = hapd->conf->vlan; vlan; vlan = vlan->next) {
|
||||
if (!vlan_compare(&vlan->vlan_desc, vlan_desc))
|
||||
break;
|
||||
if (vlan->vlan_id == VLAN_ID_WILDCARD)
|
||||
wildcard_vlan = vlan;
|
||||
}
|
||||
if (vlan) {
|
||||
vlan_id = vlan->vlan_id;
|
||||
} else if (wildcard_vlan) {
|
||||
vlan = wildcard_vlan;
|
||||
vlan_id = vlan_desc->untagged;
|
||||
} else {
|
||||
hostapd_logger(hapd, sta->addr,
|
||||
HOSTAPD_MODULE_IEEE80211,
|
||||
HOSTAPD_LEVEL_DEBUG,
|
||||
"missing VLAN and wildcard for vlan=%d",
|
||||
vlan_desc->untagged);
|
||||
vlan_id = 0;
|
||||
ret = -1;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
if (vlan && vlan->vlan_id == VLAN_ID_WILDCARD) {
|
||||
vlan = vlan_add_dynamic(hapd, vlan, vlan_id, vlan_desc);
|
||||
if (vlan == NULL) {
|
||||
hostapd_logger(hapd, sta->addr,
|
||||
HOSTAPD_MODULE_IEEE80211,
|
||||
HOSTAPD_LEVEL_DEBUG,
|
||||
"could not add dynamic VLAN interface for vlan=%d",
|
||||
vlan_desc->untagged);
|
||||
vlan_id = 0;
|
||||
ret = -1;
|
||||
goto done;
|
||||
}
|
||||
|
||||
hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
|
||||
HOSTAPD_LEVEL_DEBUG,
|
||||
"added new dynamic VLAN interface '%s'",
|
||||
vlan->ifname);
|
||||
} else if (vlan && vlan->dynamic_vlan > 0) {
|
||||
vlan->dynamic_vlan++;
|
||||
hostapd_logger(hapd, sta->addr,
|
||||
HOSTAPD_MODULE_IEEE80211,
|
||||
HOSTAPD_LEVEL_DEBUG,
|
||||
"updated existing dynamic VLAN interface '%s'",
|
||||
vlan->ifname);
|
||||
}
|
||||
done:
|
||||
old_vlan_id = sta->vlan_id;
|
||||
sta->vlan_id = vlan_id;
|
||||
sta->vlan_desc = vlan ? &vlan->vlan_desc : NULL;
|
||||
|
||||
if (vlan_id != old_vlan_id && old_vlan_id)
|
||||
vlan_remove_dynamic(hapd, old_vlan_id);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
int ap_sta_bind_vlan(struct hostapd_data *hapd, struct sta_info *sta)
|
||||
{
|
||||
#ifndef CONFIG_NO_VLAN
|
||||
@ -814,20 +889,11 @@ int ap_sta_bind_vlan(struct hostapd_data *hapd, struct sta_info *sta)
|
||||
if (hapd->conf->ssid.vlan[0])
|
||||
iface = hapd->conf->ssid.vlan;
|
||||
|
||||
if (hapd->conf->ssid.dynamic_vlan == DYNAMIC_VLAN_DISABLED)
|
||||
sta->vlan_id = 0;
|
||||
else if (sta->vlan_id > 0) {
|
||||
struct hostapd_vlan *wildcard_vlan = NULL;
|
||||
vlan = hapd->conf->vlan;
|
||||
while (vlan) {
|
||||
if (sta->vlan_id > 0) {
|
||||
for (vlan = hapd->conf->vlan; vlan; vlan = vlan->next) {
|
||||
if (vlan->vlan_id == sta->vlan_id)
|
||||
break;
|
||||
if (vlan->vlan_id == VLAN_ID_WILDCARD)
|
||||
wildcard_vlan = vlan;
|
||||
vlan = vlan->next;
|
||||
}
|
||||
if (!vlan)
|
||||
vlan = wildcard_vlan;
|
||||
if (vlan)
|
||||
iface = vlan->ifname;
|
||||
}
|
||||
@ -847,24 +913,7 @@ int ap_sta_bind_vlan(struct hostapd_data *hapd, struct sta_info *sta)
|
||||
sta->vlan_id);
|
||||
ret = -1;
|
||||
goto done;
|
||||
} else if (sta->vlan_id > 0 && vlan->vlan_id == VLAN_ID_WILDCARD) {
|
||||
vlan = vlan_add_dynamic(hapd, vlan, sta->vlan_id);
|
||||
if (vlan == NULL) {
|
||||
hostapd_logger(hapd, sta->addr,
|
||||
HOSTAPD_MODULE_IEEE80211,
|
||||
HOSTAPD_LEVEL_DEBUG, "could not add "
|
||||
"dynamic VLAN interface for vlan_id=%d",
|
||||
sta->vlan_id);
|
||||
ret = -1;
|
||||
goto done;
|
||||
}
|
||||
|
||||
iface = vlan->ifname;
|
||||
hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
|
||||
HOSTAPD_LEVEL_DEBUG, "added new dynamic VLAN "
|
||||
"interface '%s'", iface);
|
||||
} else if (vlan && vlan->vlan_id == sta->vlan_id &&
|
||||
vlan->dynamic_vlan > 0) {
|
||||
} else if (vlan && vlan->dynamic_vlan > 0) {
|
||||
vlan->dynamic_vlan++;
|
||||
hostapd_logger(hapd, sta->addr,
|
||||
HOSTAPD_MODULE_IEEE80211,
|
||||
|
@ -15,6 +15,7 @@
|
||||
#endif /* CONFIG_MESH */
|
||||
|
||||
#include "list.h"
|
||||
#include "vlan.h"
|
||||
|
||||
/* STA flags */
|
||||
#define WLAN_STA_AUTH BIT(0)
|
||||
@ -118,6 +119,7 @@ struct sta_info {
|
||||
struct rsn_preauth_interface *preauth_iface;
|
||||
|
||||
int vlan_id; /* 0: none, >0: VID */
|
||||
struct vlan_description *vlan_desc;
|
||||
int vlan_id_bound; /* updated by ap_sta_bind_vlan() */
|
||||
/* PSKs from RADIUS authentication server */
|
||||
struct hostapd_sta_wpa_psk_short *psk;
|
||||
@ -220,6 +222,8 @@ int ap_sta_wps_cancel(struct hostapd_data *hapd,
|
||||
struct sta_info *sta, void *ctx);
|
||||
#endif /* CONFIG_WPS */
|
||||
int ap_sta_bind_vlan(struct hostapd_data *hapd, struct sta_info *sta);
|
||||
int ap_sta_set_vlan(struct hostapd_data *hapd, struct sta_info *sta,
|
||||
struct vlan_description *vlan_desc);
|
||||
void ap_sta_start_sa_query(struct hostapd_data *hapd, struct sta_info *sta);
|
||||
void ap_sta_stop_sa_query(struct hostapd_data *hapd, struct sta_info *sta);
|
||||
int ap_check_sa_query_timeout(struct hostapd_data *hapd, struct sta_info *sta);
|
||||
|
29
src/ap/vlan.c
Normal file
29
src/ap/vlan.c
Normal file
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* hostapd / VLAN definition
|
||||
* Copyright (c) 2016, Jouni Malinen <j@w1.fi>
|
||||
*
|
||||
* This software may be distributed under the terms of the BSD license.
|
||||
* See README for more details.
|
||||
*/
|
||||
|
||||
#include "utils/includes.h"
|
||||
|
||||
#include "utils/common.h"
|
||||
#include "ap/vlan.h"
|
||||
|
||||
/* compare the two arguments, NULL is treated as empty
|
||||
* return zero iff they are equal
|
||||
*/
|
||||
int vlan_compare(struct vlan_description *a, struct vlan_description *b)
|
||||
{
|
||||
const int a_empty = !a || !a->notempty;
|
||||
const int b_empty = !b || !b->notempty;
|
||||
|
||||
if (a_empty && b_empty)
|
||||
return 0;
|
||||
if (a_empty || b_empty)
|
||||
return 1;
|
||||
if (a->untagged != b->untagged)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
27
src/ap/vlan.h
Normal file
27
src/ap/vlan.h
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* hostapd / VLAN definition
|
||||
* Copyright (c) 2015, Jouni Malinen <j@w1.fi>
|
||||
*
|
||||
* This software may be distributed under the terms of the BSD license.
|
||||
* See README for more details.
|
||||
*/
|
||||
|
||||
#ifndef VLAN_H
|
||||
#define VLAN_H
|
||||
|
||||
struct vlan_description {
|
||||
int notempty; /* 0 : no vlan information present, 1: else */
|
||||
int untagged; /* >0 802.1q vid */
|
||||
};
|
||||
|
||||
#ifndef CONFIG_NO_VLAN
|
||||
int vlan_compare(struct vlan_description *a, struct vlan_description *b);
|
||||
#else /* CONFIG_NO_VLAN */
|
||||
static inline int
|
||||
vlan_compare(struct vlan_description *a, struct vlan_description *b)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif /* CONFIG_NO_VLAN */
|
||||
|
||||
#endif /* VLAN_H */
|
@ -630,25 +630,26 @@ static void vlan_newlink(char *ifname, struct hostapd_data *hapd)
|
||||
struct hostapd_vlan *vlan = hapd->conf->vlan;
|
||||
char *tagged_interface = hapd->conf->ssid.vlan_tagged_interface;
|
||||
int vlan_naming = hapd->conf->ssid.vlan_naming;
|
||||
int clean;
|
||||
int clean, untagged;
|
||||
|
||||
wpa_printf(MSG_DEBUG, "VLAN: vlan_newlink(%s)", ifname);
|
||||
|
||||
while (vlan) {
|
||||
untagged = vlan->vlan_desc.untagged;
|
||||
if (os_strcmp(ifname, vlan->ifname) == 0 && !vlan->configured) {
|
||||
vlan->configured = 1;
|
||||
|
||||
if (hapd->conf->vlan_bridge[0]) {
|
||||
os_snprintf(br_name, sizeof(br_name), "%s%d",
|
||||
hapd->conf->vlan_bridge,
|
||||
vlan->vlan_id);
|
||||
untagged);
|
||||
} else if (tagged_interface) {
|
||||
os_snprintf(br_name, sizeof(br_name),
|
||||
"br%s.%d", tagged_interface,
|
||||
vlan->vlan_id);
|
||||
untagged);
|
||||
} else {
|
||||
os_snprintf(br_name, sizeof(br_name),
|
||||
"brvlan%d", vlan->vlan_id);
|
||||
"brvlan%d", untagged);
|
||||
}
|
||||
|
||||
dyn_iface_get(hapd, br_name,
|
||||
@ -662,15 +663,15 @@ static void vlan_newlink(char *ifname, struct hostapd_data *hapd)
|
||||
os_snprintf(vlan_ifname,
|
||||
sizeof(vlan_ifname),
|
||||
"%s.%d", tagged_interface,
|
||||
vlan->vlan_id);
|
||||
untagged);
|
||||
else
|
||||
os_snprintf(vlan_ifname,
|
||||
sizeof(vlan_ifname),
|
||||
"vlan%d", vlan->vlan_id);
|
||||
"vlan%d", untagged);
|
||||
|
||||
clean = 0;
|
||||
ifconfig_up(tagged_interface);
|
||||
if (!vlan_add(tagged_interface, vlan->vlan_id,
|
||||
if (!vlan_add(tagged_interface, untagged,
|
||||
vlan_ifname))
|
||||
clean |= DVLAN_CLEAN_VLAN;
|
||||
|
||||
@ -701,26 +702,27 @@ static void vlan_dellink(char *ifname, struct hostapd_data *hapd)
|
||||
struct hostapd_vlan *first, *prev, *vlan = hapd->conf->vlan;
|
||||
char *tagged_interface = hapd->conf->ssid.vlan_tagged_interface;
|
||||
int vlan_naming = hapd->conf->ssid.vlan_naming;
|
||||
int clean;
|
||||
int clean, untagged;
|
||||
|
||||
wpa_printf(MSG_DEBUG, "VLAN: vlan_dellink(%s)", ifname);
|
||||
|
||||
first = prev = vlan;
|
||||
|
||||
while (vlan) {
|
||||
untagged = vlan->vlan_desc.untagged;
|
||||
if (os_strcmp(ifname, vlan->ifname) == 0 &&
|
||||
vlan->configured) {
|
||||
if (hapd->conf->vlan_bridge[0]) {
|
||||
os_snprintf(br_name, sizeof(br_name), "%s%d",
|
||||
hapd->conf->vlan_bridge,
|
||||
vlan->vlan_id);
|
||||
untagged);
|
||||
} else if (tagged_interface) {
|
||||
os_snprintf(br_name, sizeof(br_name),
|
||||
"br%s.%d", tagged_interface,
|
||||
vlan->vlan_id);
|
||||
untagged);
|
||||
} else {
|
||||
os_snprintf(br_name, sizeof(br_name),
|
||||
"brvlan%d", vlan->vlan_id);
|
||||
"brvlan%d", untagged);
|
||||
}
|
||||
|
||||
if (vlan->clean & DVLAN_CLEAN_WLAN_PORT)
|
||||
@ -732,11 +734,11 @@ static void vlan_dellink(char *ifname, struct hostapd_data *hapd)
|
||||
os_snprintf(vlan_ifname,
|
||||
sizeof(vlan_ifname),
|
||||
"%s.%d", tagged_interface,
|
||||
vlan->vlan_id);
|
||||
untagged);
|
||||
else
|
||||
os_snprintf(vlan_ifname,
|
||||
sizeof(vlan_ifname),
|
||||
"vlan%d", vlan->vlan_id);
|
||||
"vlan%d", untagged);
|
||||
|
||||
clean = dyn_iface_put(hapd, vlan_ifname);
|
||||
|
||||
@ -1037,13 +1039,13 @@ void vlan_deinit(struct hostapd_data *hapd)
|
||||
|
||||
struct hostapd_vlan * vlan_add_dynamic(struct hostapd_data *hapd,
|
||||
struct hostapd_vlan *vlan,
|
||||
int vlan_id)
|
||||
int vlan_id,
|
||||
struct vlan_description *vlan_desc)
|
||||
{
|
||||
struct hostapd_vlan *n = NULL;
|
||||
char *ifname, *pos;
|
||||
|
||||
if (vlan == NULL || vlan_id <= 0 || vlan_id > MAX_VLAN_ID ||
|
||||
vlan->vlan_id != VLAN_ID_WILDCARD)
|
||||
if (vlan == NULL || vlan->vlan_id != VLAN_ID_WILDCARD)
|
||||
return NULL;
|
||||
|
||||
wpa_printf(MSG_DEBUG, "VLAN: %s(vlan_id=%d ifname=%s)",
|
||||
@ -1061,6 +1063,8 @@ struct hostapd_vlan * vlan_add_dynamic(struct hostapd_data *hapd,
|
||||
goto free_ifname;
|
||||
|
||||
n->vlan_id = vlan_id;
|
||||
if (vlan_desc)
|
||||
n->vlan_desc = *vlan_desc;
|
||||
n->dynamic_vlan = 1;
|
||||
|
||||
os_snprintf(n->ifname, sizeof(n->ifname), "%s%d%s", ifname, vlan_id,
|
||||
@ -1087,7 +1091,7 @@ int vlan_remove_dynamic(struct hostapd_data *hapd, int vlan_id)
|
||||
{
|
||||
struct hostapd_vlan *vlan;
|
||||
|
||||
if (vlan_id <= 0 || vlan_id > MAX_VLAN_ID)
|
||||
if (vlan_id <= 0)
|
||||
return 1;
|
||||
|
||||
wpa_printf(MSG_DEBUG, "VLAN: %s(ifname=%s vlan_id=%d)",
|
||||
|
@ -15,7 +15,8 @@ int vlan_init(struct hostapd_data *hapd);
|
||||
void vlan_deinit(struct hostapd_data *hapd);
|
||||
struct hostapd_vlan * vlan_add_dynamic(struct hostapd_data *hapd,
|
||||
struct hostapd_vlan *vlan,
|
||||
int vlan_id);
|
||||
int vlan_id,
|
||||
struct vlan_description *vlan_desc);
|
||||
int vlan_remove_dynamic(struct hostapd_data *hapd, int vlan_id);
|
||||
#else /* CONFIG_NO_VLAN */
|
||||
static inline int vlan_init(struct hostapd_data *hapd)
|
||||
@ -27,9 +28,9 @@ static inline void vlan_deinit(struct hostapd_data *hapd)
|
||||
{
|
||||
}
|
||||
|
||||
static inline struct hostapd_vlan * vlan_add_dynamic(struct hostapd_data *hapd,
|
||||
struct hostapd_vlan *vlan,
|
||||
int vlan_id)
|
||||
static inline struct hostapd_vlan *
|
||||
vlan_add_dynamic(struct hostapd_data *hapd, struct hostapd_vlan *vlan,
|
||||
int vlan_id, struct vlan_description *vlan_desc)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
@ -712,11 +712,13 @@ int wpa_validate_wpa_ie(struct wpa_authenticator *wpa_auth,
|
||||
}
|
||||
}
|
||||
if (sm->pmksa && pmkid) {
|
||||
struct vlan_description *vlan_desc;
|
||||
|
||||
vlan_desc = sm->pmksa->vlan_desc;
|
||||
wpa_auth_vlogger(wpa_auth, sm->addr, LOGGER_DEBUG,
|
||||
"PMKID found from PMKSA cache "
|
||||
"eap_type=%d vlan_id=%d",
|
||||
"PMKID found from PMKSA cache eap_type=%d vlan=%d",
|
||||
sm->pmksa->eap_type_authsrv,
|
||||
sm->pmksa->vlan_id);
|
||||
vlan_desc ? vlan_desc->untagged : 0);
|
||||
os_memcpy(wpa_auth->dot11RSNAPMKIDUsed, pmkid, PMKID_LEN);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user