diff --git a/hostapd/config.c b/hostapd/config.c index 1e89ad39d..cb8f2107c 100644 --- a/hostapd/config.c +++ b/hostapd/config.c @@ -1534,6 +1534,8 @@ struct hostapd_config * hostapd_config_read(const char *fname) line, pos); errors++; } + } else if (os_strcmp(buf, "wds_sta") == 0) { + bss->wds_sta = atoi(pos); } else if (os_strcmp(buf, "ap_max_inactivity") == 0) { bss->ap_max_inactivity = atoi(pos); } else if (os_strcmp(buf, "country_code") == 0) { diff --git a/hostapd/config.h b/hostapd/config.h index 0201a1d10..467c382cc 100644 --- a/hostapd/config.h +++ b/hostapd/config.h @@ -194,6 +194,7 @@ struct hostapd_bss_config { int num_accept_mac; struct mac_acl_entry *deny_mac; int num_deny_mac; + int wds_sta; int auth_algs; /* bitfield of allowed IEEE 802.11 authentication * algorithms, WPA_AUTH_ALG_{OPEN,SHARED,LEAP} */ diff --git a/hostapd/driver_i.h b/hostapd/driver_i.h index e5c85a89f..b73630c2b 100644 --- a/hostapd/driver_i.h +++ b/hostapd/driver_i.h @@ -399,6 +399,15 @@ hostapd_set_sta_vlan(const char *ifname, struct hostapd_data *hapd, return hapd->driver->set_sta_vlan(hapd->drv_priv, addr, ifname, vlan_id); } +static inline int +hostapd_set_wds_sta(struct hostapd_data *hapd, const u8 *addr, int aid, + int val) +{ + if (hapd->driver == NULL || hapd->driver->set_wds_sta == NULL) + return 0; + return hapd->driver->set_wds_sta(hapd->drv_priv, addr, aid, val); +} + static inline int hostapd_driver_commit(struct hostapd_data *hapd) { diff --git a/hostapd/drv_callbacks.c b/hostapd/drv_callbacks.c index e1a7e4c01..bb6e5f976 100644 --- a/hostapd/drv_callbacks.c +++ b/hostapd/drv_callbacks.c @@ -299,6 +299,7 @@ static const u8 * get_hdr_bssid(const struct ieee80211_hdr *hdr, size_t len) if (len < 24) return NULL; switch (fc & (WLAN_FC_FROMDS | WLAN_FC_TODS)) { + case WLAN_FC_FROMDS | WLAN_FC_TODS: case WLAN_FC_TODS: return hdr->addr1; case WLAN_FC_FROMDS: @@ -344,11 +345,14 @@ static void hostapd_rx_from_unknown_sta(struct hostapd_data *hapd, const struct ieee80211_hdr *hdr, size_t len) { + u16 fc = le_to_host16(hdr->frame_control); hapd = get_hapd_bssid(hapd->iface, get_hdr_bssid(hdr, len)); if (hapd == NULL || hapd == HAPD_BROADCAST) return; - ieee802_11_rx_from_unknown(hapd, hdr->addr2); + ieee802_11_rx_from_unknown(hapd, hdr->addr2, + (fc & (WLAN_FC_TODS | WLAN_FC_FROMDS)) == + (WLAN_FC_TODS | WLAN_FC_FROMDS)); } diff --git a/hostapd/dump_state.c b/hostapd/dump_state.c index 408ef1e49..0a191dc79 100644 --- a/hostapd/dump_state.c +++ b/hostapd/dump_state.c @@ -106,7 +106,7 @@ static void hostapd_dump_state(struct hostapd_data *hapd) fprintf(f, "\nSTA=" MACSTR "\n", MAC2STR(sta->addr)); fprintf(f, - " AID=%d flags=0x%x %s%s%s%s%s%s%s%s%s%s%s%s%s%s\n" + " AID=%d flags=0x%x %s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n" " capability=0x%x listen_interval=%d\n", sta->aid, sta->flags, @@ -126,6 +126,7 @@ static void hostapd_dump_state(struct hostapd_data *hapd) (sta->flags & WLAN_STA_MFP ? "[MFP]" : ""), (sta->flags & WLAN_STA_WPS ? "[WPS]" : ""), (sta->flags & WLAN_STA_MAYBE_WPS ? "[MAYBE_WPS]" : ""), + (sta->flags & WLAN_STA_WDS ? "[WDS]" : ""), (sta->flags & WLAN_STA_NONERP ? "[NonERP]" : ""), sta->capability, sta->listen_interval); diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf index 625449663..2521b7440 100644 --- a/hostapd/hostapd.conf +++ b/hostapd/hostapd.conf @@ -346,6 +346,12 @@ wmm_ac_vo_acm=0 # remain asleep). Default: 65535 (no limit apart from field size) #max_listen_interval=100 +# WDS (4-address frame) mode with per-station virtual interfaces +# (only supported with driver=nl80211) +# This mode allows associated stations to use 4-address frames to allow layer 2 +# bridging to be used. +#wds_sta=1 + ##### IEEE 802.11n related configuration ###################################### # ieee80211n: Whether IEEE 802.11n (HT) is enabled diff --git a/hostapd/ieee802_11.c b/hostapd/ieee802_11.c index 8d4fe3923..df18a9341 100644 --- a/hostapd/ieee802_11.c +++ b/hostapd/ieee802_11.c @@ -1689,13 +1689,22 @@ void hostapd_tx_status(struct hostapd_data *hapd, const u8 *addr, } -void ieee802_11_rx_from_unknown(struct hostapd_data *hapd, const u8 *src) +void ieee802_11_rx_from_unknown(struct hostapd_data *hapd, const u8 *src, + int wds) { struct sta_info *sta; sta = ap_get_sta(hapd, src); - if (sta && (sta->flags & WLAN_STA_ASSOC)) + if (sta && (sta->flags & WLAN_STA_ASSOC)) { + if (wds && !(sta->flags & WLAN_STA_WDS)) { + wpa_printf(MSG_DEBUG, "Enable 4-address WDS mode for " + "STA " MACSTR " (aid %u)", + MAC2STR(sta->addr), sta->aid); + sta->flags |= WLAN_STA_WDS; + hostapd_set_wds_sta(hapd, sta->addr, sta->aid, 1); + } return; + } wpa_printf(MSG_DEBUG, "Data/PS-poll frame from not associated STA " MACSTR, MAC2STR(src)); diff --git a/hostapd/ieee802_11.h b/hostapd/ieee802_11.h index 95c99b2d3..3347256ee 100644 --- a/hostapd/ieee802_11.h +++ b/hostapd/ieee802_11.h @@ -65,6 +65,7 @@ u16 copy_sta_ht_capab(struct sta_info *sta, const u8 *ht_capab, void update_ht_state(struct hostapd_data *hapd, struct sta_info *sta); void hostapd_tx_status(struct hostapd_data *hapd, const u8 *addr, const u8 *buf, size_t len, int ack); -void ieee802_11_rx_from_unknown(struct hostapd_data *hapd, const u8 *src); +void ieee802_11_rx_from_unknown(struct hostapd_data *hapd, const u8 *src, + int wds); #endif /* IEEE802_11_H */ diff --git a/hostapd/sta_info.c b/hostapd/sta_info.c index 2a2d7bacc..4d9ba429a 100644 --- a/hostapd/sta_info.c +++ b/hostapd/sta_info.c @@ -120,6 +120,7 @@ void ap_free_sta(struct hostapd_data *hapd, struct sta_info *sta) accounting_sta_stop(hapd, sta); + hostapd_set_wds_sta(hapd, sta->addr, sta->aid, 0); if (!ap_sta_in_other_bss(hapd, sta, WLAN_STA_ASSOC) && !(sta->flags & WLAN_STA_PREAUTH)) hostapd_sta_remove(hapd, sta->addr); diff --git a/hostapd/sta_info.h b/hostapd/sta_info.h index 6a98387aa..f15a1b0d3 100644 --- a/hostapd/sta_info.h +++ b/hostapd/sta_info.h @@ -30,6 +30,7 @@ #define WLAN_STA_HT BIT(11) #define WLAN_STA_WPS BIT(12) #define WLAN_STA_MAYBE_WPS BIT(13) +#define WLAN_STA_WDS BIT(14) #define WLAN_STA_NONERP BIT(31) /* Maximum number of supported rates (from both Supported Rates and Extended diff --git a/src/drivers/driver.h b/src/drivers/driver.h index 7fa4ed2a0..aa11259e5 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -1527,6 +1527,16 @@ struct wpa_driver_ops { * Returns: 0 on success, -1 on failure */ int (*set_supp_port)(void *priv, int authorized); + + /** + * set_wds_sta - Bind a station into a 4-address WDS (AP only) + * @priv: Private driver interface data + * @addr: MAC address of the associated station + * @aid: Association ID + * @val: 1 = bind to 4-address WDS; 0 = unbind + * Returns: 0 on success, -1 on failure + */ + int (*set_wds_sta)(void *priv, const u8 *addr, int aid, int val); }; /** diff --git a/src/drivers/driver_ndis.c b/src/drivers/driver_ndis.c index bec1e8d8c..034be9576 100644 --- a/src/drivers/driver_ndis.c +++ b/src/drivers/driver_ndis.c @@ -3244,5 +3244,6 @@ const struct wpa_driver_ops wpa_driver_ndis_ops = { NULL /* set_ht_params */, NULL /* set_wps_beacon_ie */, NULL /* set_wps_probe_resp_ie */, - NULL /* set_supp_port */ + NULL /* set_supp_port */, + NULL /* set_wds_sta */ }; diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c index 697fefd9f..67043a622 100644 --- a/src/drivers/driver_nl80211.c +++ b/src/drivers/driver_nl80211.c @@ -128,6 +128,9 @@ static void del_ifidx(struct wpa_driver_nl80211_data *drv, int ifidx); static struct i802_bss * get_bss(struct wpa_driver_nl80211_data *drv, int ifindex); static int i802_set_freq(void *priv, struct hostapd_freq_params *freq); +static int wpa_driver_nl80211_if_remove(void *priv, + enum wpa_driver_if_type type, + const char *ifname); #endif /* HOSTAPD */ @@ -2553,7 +2556,7 @@ static void nl80211_remove_iface(struct wpa_driver_nl80211_data *drv, static int nl80211_create_iface_once(struct wpa_driver_nl80211_data *drv, const char *ifname, enum nl80211_iftype iftype, - const u8 *addr) + const u8 *addr, int wds) { struct nl_msg *msg, *flags = NULL; int ifidx; @@ -2584,6 +2587,8 @@ static int nl80211_create_iface_once(struct wpa_driver_nl80211_data *drv, if (err) goto nla_put_failure; + } else if (wds) { + NLA_PUT_U8(msg, NL80211_ATTR_4ADDR, wds); } ret = send_and_recv_msgs(drv, msg, NULL, NULL); @@ -2616,11 +2621,11 @@ static int nl80211_create_iface_once(struct wpa_driver_nl80211_data *drv, static int nl80211_create_iface(struct wpa_driver_nl80211_data *drv, const char *ifname, enum nl80211_iftype iftype, - const u8 *addr) + const u8 *addr, int wds) { int ret; - ret = nl80211_create_iface_once(drv, ifname, iftype, addr); + ret = nl80211_create_iface_once(drv, ifname, iftype, addr, wds); /* if error occured and interface exists already */ if (ret == -ENFILE && if_nametoindex(ifname)) { @@ -2630,7 +2635,8 @@ static int nl80211_create_iface(struct wpa_driver_nl80211_data *drv, nl80211_remove_iface(drv, if_nametoindex(ifname)); /* Try to create the interface again */ - ret = nl80211_create_iface_once(drv, ifname, iftype, addr); + ret = nl80211_create_iface_once(drv, ifname, iftype, addr, + wds); } return ret; @@ -2817,7 +2823,7 @@ static struct sock_filter msock_filter_insns[] = { #if 0 /* - * drop non-data frames, WDS frames + * drop non-data frames */ /* load the lower byte of the frame control field */ BPF_STMT(BPF_LD | BPF_B | BPF_IND, 0), @@ -2825,13 +2831,13 @@ static struct sock_filter msock_filter_insns[] = { BPF_STMT(BPF_ALU | BPF_AND | BPF_K, 0x0c), /* drop non-data frames */ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 8, 0, FAIL), +#endif /* load the upper byte of the frame control field */ - BPF_STMT(BPF_LD | BPF_B | BPF_IND, 0), + BPF_STMT(BPF_LD | BPF_B | BPF_IND, 1), /* mask off toDS/fromDS */ BPF_STMT(BPF_ALU | BPF_AND | BPF_K, 0x03), - /* drop WDS frames */ - BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 3, FAIL, 0), -#endif + /* accept WDS frames */ + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 3, PASS, 0), /* * add header length to index @@ -2937,7 +2943,8 @@ nl80211_create_monitor_interface(struct wpa_driver_nl80211_data *drv) buf[IFNAMSIZ - 1] = '\0'; drv->monitor_ifidx = - nl80211_create_iface(drv, buf, NL80211_IFTYPE_MONITOR, NULL); + nl80211_create_iface(drv, buf, NL80211_IFTYPE_MONITOR, NULL, + 0); if (drv->monitor_ifidx < 0) return -1; @@ -4065,6 +4072,26 @@ static int i802_set_sta_vlan(void *priv, const u8 *addr, } +static int i802_set_wds_sta(void *priv, const u8 *addr, int aid, int val) +{ + struct wpa_driver_nl80211_data *drv = priv; + char name[16]; + + os_snprintf(name, sizeof(name), "%s.sta%d", drv->ifname, aid); + if (val) { + if (nl80211_create_iface(priv, name, NL80211_IFTYPE_AP_VLAN, + NULL, 1) < 0) + return -1; + hostapd_set_iface_flags(drv, name, 1); + return i802_set_sta_vlan(priv, addr, name, 0); + } else { + i802_set_sta_vlan(priv, addr, drv->ifname, 0); + return wpa_driver_nl80211_if_remove(priv, WPA_IF_AP_VLAN, + name); + } +} + + static void handle_eapol(int sock, void *eloop_ctx, void *sock_ctx) { struct wpa_driver_nl80211_data *drv = eloop_ctx; @@ -4262,7 +4289,8 @@ static int wpa_driver_nl80211_if_add(const char *iface, void *priv, #endif /* HOSTAPD */ ifidx = nl80211_create_iface(drv, ifname, - wpa_driver_nl80211_if_type(type), addr); + wpa_driver_nl80211_if_type(type), addr, + 0); if (ifidx < 0) { #ifdef HOSTAPD os_free(bss); @@ -4363,5 +4391,6 @@ const struct wpa_driver_ops wpa_driver_nl80211_ops = { .set_short_slot_time = i802_set_short_slot_time, .set_tx_queue_params = i802_set_tx_queue_params, .set_sta_vlan = i802_set_sta_vlan, + .set_wds_sta = i802_set_wds_sta, #endif /* HOSTAPD */ }; diff --git a/wpa_supplicant/ap.c b/wpa_supplicant/ap.c index 2e52adf21..1cbcde05c 100644 --- a/wpa_supplicant/ap.c +++ b/wpa_supplicant/ap.c @@ -507,7 +507,10 @@ void ap_rx_from_unknown_sta(void *ctx, const struct ieee80211_hdr *hdr, { #ifdef NEED_AP_MLME struct wpa_supplicant *wpa_s = ctx; - ieee802_11_rx_from_unknown(wpa_s->ap_iface->bss[0], hdr->addr2); + u16 fc = le_to_host16(hdr->frame_control); + ieee802_11_rx_from_unknown(wpa_s->ap_iface->bss[0], hdr->addr2, + (fc & (WLAN_FC_TODS | WLAN_FC_FROMDS)) == + (WLAN_FC_TODS | WLAN_FC_FROMDS)); #endif /* NEED_AP_MLME */ }