Skip to content
Snippets Groups Projects
hostap_ap.c 84.8 KiB
Newer Older
  • Learn to ignore specific revisions
  • /*
     * Intersil Prism2 driver with Host AP (software access point) support
     * Copyright (c) 2001-2002, SSH Communications Security Corp and Jouni Malinen
    
     * <j@w1.fi>
     * Copyright (c) 2002-2005, Jouni Malinen <j@w1.fi>
    
     *
     * This file is to be included into hostap.c when S/W AP functionality is
     * compiled.
     *
     * AP:  FIX:
     * - if unicast Class 2 (assoc,reassoc,disassoc) frame received from
     *   unauthenticated STA, send deauth. frame (8802.11: 5.5)
     * - if unicast Class 3 (data with to/from DS,deauth,pspoll) frame received
     *   from authenticated, but unassoc STA, send disassoc frame (8802.11: 5.5)
     * - if unicast Class 3 received from unauthenticated STA, send deauth. frame
     *   (8802.11: 5.5)
     */
    
    
    #include <linux/proc_fs.h>
    #include <linux/delay.h>
    #include <linux/random.h>
    
    #include <linux/moduleparam.h>
    
    
    #include "hostap_wlan.h"
    #include "hostap.h"
    #include "hostap_ap.h"
    
    
    static int other_ap_policy[MAX_PARM_DEVICES] = { AP_OTHER_AP_SKIP_ALL,
    						 DEF_INTS };
    module_param_array(other_ap_policy, int, NULL, 0444);
    MODULE_PARM_DESC(other_ap_policy, "Other AP beacon monitoring policy (0-3)");
    
    static int ap_max_inactivity[MAX_PARM_DEVICES] = { AP_MAX_INACTIVITY_SEC,
    						   DEF_INTS };
    module_param_array(ap_max_inactivity, int, NULL, 0444);
    MODULE_PARM_DESC(ap_max_inactivity, "AP timeout (in seconds) for station "
    		 "inactivity");
    
    static int ap_bridge_packets[MAX_PARM_DEVICES] = { 1, DEF_INTS };
    module_param_array(ap_bridge_packets, int, NULL, 0444);
    MODULE_PARM_DESC(ap_bridge_packets, "Bridge packets directly between "
    		 "stations");
    
    static int autom_ap_wds[MAX_PARM_DEVICES] = { 0, DEF_INTS };
    module_param_array(autom_ap_wds, int, NULL, 0444);
    MODULE_PARM_DESC(autom_ap_wds, "Add WDS connections to other APs "
    		 "automatically");
    
    
    static struct sta_info* ap_get_sta(struct ap_data *ap, u8 *sta);
    static void hostap_event_expired_sta(struct net_device *dev,
    				     struct sta_info *sta);
    
    static void handle_add_proc_queue(struct work_struct *work);
    
    
    #ifndef PRISM2_NO_KERNEL_IEEE80211_MGMT
    
    static void handle_wds_oper_queue(struct work_struct *work);
    
    static void prism2_send_mgmt(struct net_device *dev,
    
    			     u16 type_subtype, char *body,
    
    			     int body_len, u8 *addr, u16 tx_cb_idx);
    #endif /* PRISM2_NO_KERNEL_IEEE80211_MGMT */
    
    
    #ifndef PRISM2_NO_PROCFS_DEBUG
    static int ap_debug_proc_read(char *page, char **start, off_t off,
    			      int count, int *eof, void *data)
    {
    	char *p = page;
    	struct ap_data *ap = (struct ap_data *) data;
    
    	if (off != 0) {
    		*eof = 1;
    		return 0;
    	}
    
    	p += sprintf(p, "BridgedUnicastFrames=%u\n", ap->bridged_unicast);
    	p += sprintf(p, "BridgedMulticastFrames=%u\n", ap->bridged_multicast);
    	p += sprintf(p, "max_inactivity=%u\n", ap->max_inactivity / HZ);
    	p += sprintf(p, "bridge_packets=%u\n", ap->bridge_packets);
    	p += sprintf(p, "nullfunc_ack=%u\n", ap->nullfunc_ack);
    	p += sprintf(p, "autom_ap_wds=%u\n", ap->autom_ap_wds);
    	p += sprintf(p, "auth_algs=%u\n", ap->local->auth_algs);
    	p += sprintf(p, "tx_drop_nonassoc=%u\n", ap->tx_drop_nonassoc);
    
    	return (p - page);
    }
    #endif /* PRISM2_NO_PROCFS_DEBUG */
    
    
    static void ap_sta_hash_add(struct ap_data *ap, struct sta_info *sta)
    {
    	sta->hnext = ap->sta_hash[STA_HASH(sta->addr)];
    	ap->sta_hash[STA_HASH(sta->addr)] = sta;
    }
    
    static void ap_sta_hash_del(struct ap_data *ap, struct sta_info *sta)
    {
    	struct sta_info *s;
    
    	s = ap->sta_hash[STA_HASH(sta->addr)];
    	if (s == NULL) return;
    	if (memcmp(s->addr, sta->addr, ETH_ALEN) == 0) {
    		ap->sta_hash[STA_HASH(sta->addr)] = s->hnext;
    		return;
    	}
    
    	while (s->hnext != NULL && memcmp(s->hnext->addr, sta->addr, ETH_ALEN)
    	       != 0)
    		s = s->hnext;
    	if (s->hnext != NULL)
    		s->hnext = s->hnext->hnext;
    	else
    
    		printk("AP: could not remove STA %pM from hash table\n",
    		       sta->addr);
    
    }
    
    static void ap_free_sta(struct ap_data *ap, struct sta_info *sta)
    {
    	if (sta->ap && sta->local)
    		hostap_event_expired_sta(sta->local->dev, sta);
    
    	if (ap->proc != NULL) {
    		char name[20];
    
    		sprintf(name, "%pM", sta->addr);
    
    		remove_proc_entry(name, ap->proc);
    	}
    
    	if (sta->crypt) {
    		sta->crypt->ops->deinit(sta->crypt->priv);
    		kfree(sta->crypt);
    		sta->crypt = NULL;
    	}
    
    	skb_queue_purge(&sta->tx_buf);
    
    	ap->num_sta--;
    #ifndef PRISM2_NO_KERNEL_IEEE80211_MGMT
    	if (sta->aid > 0)
    		ap->sta_aid[sta->aid - 1] = NULL;
    
    	if (!sta->ap && sta->u.sta.challenge)
    		kfree(sta->u.sta.challenge);
    	del_timer(&sta->timer);
    #endif /* PRISM2_NO_KERNEL_IEEE80211_MGMT */
    
    	kfree(sta);
    }
    
    
    static void hostap_set_tim(local_info_t *local, int aid, int set)
    {
    	if (local->func->set_tim)
    		local->func->set_tim(local->dev, aid, set);
    }
    
    
    static void hostap_event_new_sta(struct net_device *dev, struct sta_info *sta)
    {
    	union iwreq_data wrqu;
    	memset(&wrqu, 0, sizeof(wrqu));
    	memcpy(wrqu.addr.sa_data, sta->addr, ETH_ALEN);
    	wrqu.addr.sa_family = ARPHRD_ETHER;
    	wireless_send_event(dev, IWEVREGISTERED, &wrqu, NULL);
    }
    
    
    static void hostap_event_expired_sta(struct net_device *dev,
    				     struct sta_info *sta)
    {
    	union iwreq_data wrqu;
    	memset(&wrqu, 0, sizeof(wrqu));
    	memcpy(wrqu.addr.sa_data, sta->addr, ETH_ALEN);
    	wrqu.addr.sa_family = ARPHRD_ETHER;
    	wireless_send_event(dev, IWEVEXPIRED, &wrqu, NULL);
    }
    
    
    #ifndef PRISM2_NO_KERNEL_IEEE80211_MGMT
    
    static void ap_handle_timer(unsigned long data)
    {
    	struct sta_info *sta = (struct sta_info *) data;
    	local_info_t *local;
    	struct ap_data *ap;
    	unsigned long next_time = 0;
    	int was_assoc;
    
    	if (sta == NULL || sta->local == NULL || sta->local->ap == NULL) {
    		PDEBUG(DEBUG_AP, "ap_handle_timer() called with NULL data\n");
    		return;
    	}
    
    	local = sta->local;
    	ap = local->ap;
    	was_assoc = sta->flags & WLAN_STA_ASSOC;
    
    	if (atomic_read(&sta->users) != 0)
    		next_time = jiffies + HZ;
    	else if ((sta->flags & WLAN_STA_PERM) && !(sta->flags & WLAN_STA_AUTH))
    		next_time = jiffies + ap->max_inactivity;
    
    	if (time_before(jiffies, sta->last_rx + ap->max_inactivity)) {
    		/* station activity detected; reset timeout state */
    		sta->timeout_next = STA_NULLFUNC;
    		next_time = sta->last_rx + ap->max_inactivity;
    	} else if (sta->timeout_next == STA_DISASSOC &&
    		   !(sta->flags & WLAN_STA_PENDING_POLL)) {
    		/* STA ACKed data nullfunc frame poll */
    		sta->timeout_next = STA_NULLFUNC;
    		next_time = jiffies + ap->max_inactivity;
    	}
    
    	if (next_time) {
    		sta->timer.expires = next_time;
    		add_timer(&sta->timer);
    		return;
    	}
    
    	if (sta->ap)
    		sta->timeout_next = STA_DEAUTH;
    
    	if (sta->timeout_next == STA_DEAUTH && !(sta->flags & WLAN_STA_PERM)) {
    		spin_lock(&ap->sta_table_lock);
    		ap_sta_hash_del(ap, sta);
    		list_del(&sta->list);
    		spin_unlock(&ap->sta_table_lock);
    		sta->flags &= ~(WLAN_STA_AUTH | WLAN_STA_ASSOC);
    	} else if (sta->timeout_next == STA_DISASSOC)
    		sta->flags &= ~WLAN_STA_ASSOC;
    
    	if (was_assoc && !(sta->flags & WLAN_STA_ASSOC) && !sta->ap)
    		hostap_event_expired_sta(local->dev, sta);
    
    	if (sta->timeout_next == STA_DEAUTH && sta->aid > 0 &&
    	    !skb_queue_empty(&sta->tx_buf)) {
    		hostap_set_tim(local, sta->aid, 0);
    		sta->flags &= ~WLAN_STA_TIM;
    	}
    
    	if (sta->ap) {
    		if (ap->autom_ap_wds) {
    			PDEBUG(DEBUG_AP, "%s: removing automatic WDS "
    
    			       "connection to AP %pM\n",
    			       local->dev->name, sta->addr);
    
    			hostap_wds_link_oper(local, sta->addr, WDS_DEL);
    		}
    	} else if (sta->timeout_next == STA_NULLFUNC) {
    		/* send data frame to poll STA and check whether this frame
    		 * is ACKed */
    
    		/* FIX: IEEE80211_STYPE_NULLFUNC would be more appropriate, but
    
    		 * it is apparently not retried so TX Exc events are not
    		 * received for it */
    		sta->flags |= WLAN_STA_PENDING_POLL;
    
    		prism2_send_mgmt(local->dev, IEEE80211_FTYPE_DATA |
    				 IEEE80211_STYPE_DATA, NULL, 0,
    
    				 sta->addr, ap->tx_callback_poll);
    	} else {
    		int deauth = sta->timeout_next == STA_DEAUTH;
    
    Al Viro's avatar
    Al Viro committed
    		__le16 resp;
    
    		PDEBUG(DEBUG_AP, "%s: sending %s info to STA %pM"
    
    		       "(last=%lu, jiffies=%lu)\n",
    		       local->dev->name,
    		       deauth ? "deauthentication" : "disassociation",
    
    		       sta->addr, sta->last_rx, jiffies);
    
    
    		resp = cpu_to_le16(deauth ? WLAN_REASON_PREV_AUTH_NOT_VALID :
    				   WLAN_REASON_DISASSOC_DUE_TO_INACTIVITY);
    
    		prism2_send_mgmt(local->dev, IEEE80211_FTYPE_MGMT |
    				 (deauth ? IEEE80211_STYPE_DEAUTH :
    				  IEEE80211_STYPE_DISASSOC),
    
    				 (char *) &resp, 2, sta->addr, 0);
    	}
    
    	if (sta->timeout_next == STA_DEAUTH) {
    		if (sta->flags & WLAN_STA_PERM) {
    
    			PDEBUG(DEBUG_AP, "%s: STA %pM"
    
    			       " would have been removed, "
    			       "but it has 'perm' flag\n",
    
    			       local->dev->name, sta->addr);
    
    		} else
    			ap_free_sta(ap, sta);
    		return;
    	}
    
    	if (sta->timeout_next == STA_NULLFUNC) {
    		sta->timeout_next = STA_DISASSOC;
    		sta->timer.expires = jiffies + AP_DISASSOC_DELAY;
    	} else {
    		sta->timeout_next = STA_DEAUTH;
    		sta->timer.expires = jiffies + AP_DEAUTH_DELAY;
    	}
    
    	add_timer(&sta->timer);
    }
    
    
    void hostap_deauth_all_stas(struct net_device *dev, struct ap_data *ap,
    			    int resend)
    {
    	u8 addr[ETH_ALEN];
    
    Al Viro's avatar
    Al Viro committed
    	__le16 resp;
    
    	int i;
    
    	PDEBUG(DEBUG_AP, "%s: Deauthenticate all stations\n", dev->name);
    	memset(addr, 0xff, ETH_ALEN);
    
    
    Al Viro's avatar
    Al Viro committed
    	resp = cpu_to_le16(WLAN_REASON_PREV_AUTH_NOT_VALID);
    
    
    	/* deauth message sent; try to resend it few times; the message is
    	 * broadcast, so it may be delayed until next DTIM; there is not much
    	 * else we can do at this point since the driver is going to be shut
    	 * down */
    	for (i = 0; i < 5; i++) {
    
    		prism2_send_mgmt(dev, IEEE80211_FTYPE_MGMT |
    				 IEEE80211_STYPE_DEAUTH,
    
    				 (char *) &resp, 2, addr, 0);
    
    		if (!resend || ap->num_sta <= 0)
    			return;
    
    		mdelay(50);
    	}
    }
    
    
    static int ap_control_proc_read(char *page, char **start, off_t off,
    				int count, int *eof, void *data)
    {
    	char *p = page;
    	struct ap_data *ap = (struct ap_data *) data;
    	char *policy_txt;
    	struct mac_entry *entry;
    
    	if (off != 0) {
    		*eof = 1;
    		return 0;
    	}
    
    	switch (ap->mac_restrictions.policy) {
    	case MAC_POLICY_OPEN:
    		policy_txt = "open";
    		break;
    	case MAC_POLICY_ALLOW:
    		policy_txt = "allow";
    		break;
    	case MAC_POLICY_DENY:
    		policy_txt = "deny";
    		break;
    	default:
    		policy_txt = "unknown";
    		break;
    
    	p += sprintf(p, "MAC policy: %s\n", policy_txt);
    	p += sprintf(p, "MAC entries: %u\n", ap->mac_restrictions.entries);
    	p += sprintf(p, "MAC list:\n");
    	spin_lock_bh(&ap->mac_restrictions.lock);
    
    	list_for_each_entry(entry, &ap->mac_restrictions.mac_list, list) {
    
    		if (p - page > PAGE_SIZE - 80) {
    			p += sprintf(p, "All entries did not fit one page.\n");
    			break;
    		}
    
    
    		p += sprintf(p, "%pM\n", entry->addr);
    
    	}
    	spin_unlock_bh(&ap->mac_restrictions.lock);
    
    	return (p - page);
    }
    
    
    
    int ap_control_add_mac(struct mac_restrictions *mac_restrictions, u8 *mac)
    
    {
    	struct mac_entry *entry;
    
    	entry = kmalloc(sizeof(struct mac_entry), GFP_KERNEL);
    	if (entry == NULL)
    
    
    	memcpy(entry->addr, mac, ETH_ALEN);
    
    	spin_lock_bh(&mac_restrictions->lock);
    	list_add_tail(&entry->list, &mac_restrictions->mac_list);
    	mac_restrictions->entries++;
    	spin_unlock_bh(&mac_restrictions->lock);
    
    	return 0;
    }
    
    
    
    int ap_control_del_mac(struct mac_restrictions *mac_restrictions, u8 *mac)
    
    {
    	struct list_head *ptr;
    	struct mac_entry *entry;
    
    	spin_lock_bh(&mac_restrictions->lock);
    	for (ptr = mac_restrictions->mac_list.next;
    	     ptr != &mac_restrictions->mac_list; ptr = ptr->next) {
    		entry = list_entry(ptr, struct mac_entry, list);
    
    		if (memcmp(entry->addr, mac, ETH_ALEN) == 0) {
    			list_del(ptr);
    			kfree(entry);
    			mac_restrictions->entries--;
    			spin_unlock_bh(&mac_restrictions->lock);
    			return 0;
    		}
    	}
    	spin_unlock_bh(&mac_restrictions->lock);
    	return -1;
    }
    
    
    static int ap_control_mac_deny(struct mac_restrictions *mac_restrictions,
    			       u8 *mac)
    {
    	struct mac_entry *entry;
    	int found = 0;
    
    	if (mac_restrictions->policy == MAC_POLICY_OPEN)
    		return 0;
    
    	spin_lock_bh(&mac_restrictions->lock);
    
    	list_for_each_entry(entry, &mac_restrictions->mac_list, list) {
    
    		if (memcmp(entry->addr, mac, ETH_ALEN) == 0) {
    			found = 1;
    			break;
    		}
    	}
    	spin_unlock_bh(&mac_restrictions->lock);
    
    	if (mac_restrictions->policy == MAC_POLICY_ALLOW)
    		return !found;
    	else
    		return found;
    }
    
    
    
    void ap_control_flush_macs(struct mac_restrictions *mac_restrictions)
    
    {
    	struct list_head *ptr, *n;
    	struct mac_entry *entry;
    
    	if (mac_restrictions->entries == 0)
    		return;
    
    	spin_lock_bh(&mac_restrictions->lock);
    	for (ptr = mac_restrictions->mac_list.next, n = ptr->next;
    	     ptr != &mac_restrictions->mac_list;
    	     ptr = n, n = ptr->next) {
    		entry = list_entry(ptr, struct mac_entry, list);
    		list_del(ptr);
    		kfree(entry);
    	}
    	mac_restrictions->entries = 0;
    	spin_unlock_bh(&mac_restrictions->lock);
    }
    
    
    
    int ap_control_kick_mac(struct ap_data *ap, struct net_device *dev, u8 *mac)
    
    {
    	struct sta_info *sta;
    
    Al Viro's avatar
    Al Viro committed
    	__le16 resp;
    
    
    	spin_lock_bh(&ap->sta_table_lock);
    	sta = ap_get_sta(ap, mac);
    	if (sta) {
    		ap_sta_hash_del(ap, sta);
    		list_del(&sta->list);
    	}
    	spin_unlock_bh(&ap->sta_table_lock);
    
    	if (!sta)
    		return -EINVAL;
    
    	resp = cpu_to_le16(WLAN_REASON_PREV_AUTH_NOT_VALID);
    
    	prism2_send_mgmt(dev, IEEE80211_FTYPE_MGMT | IEEE80211_STYPE_DEAUTH,
    
    			 (char *) &resp, 2, sta->addr, 0);
    
    	if ((sta->flags & WLAN_STA_ASSOC) && !sta->ap)
    		hostap_event_expired_sta(dev, sta);
    
    	ap_free_sta(ap, sta);
    
    	return 0;
    }
    
    #endif /* PRISM2_NO_KERNEL_IEEE80211_MGMT */
    
    
    
    void ap_control_kickall(struct ap_data *ap)
    
    {
    	struct list_head *ptr, *n;
    	struct sta_info *sta;
    
    	spin_lock_bh(&ap->sta_table_lock);
    	for (ptr = ap->sta_list.next, n = ptr->next; ptr != &ap->sta_list;
    	     ptr = n, n = ptr->next) {
    		sta = list_entry(ptr, struct sta_info, list);
    		ap_sta_hash_del(ap, sta);
    		list_del(&sta->list);
    		if ((sta->flags & WLAN_STA_ASSOC) && !sta->ap && sta->local)
    			hostap_event_expired_sta(sta->local->dev, sta);
    		ap_free_sta(ap, sta);
    	}
    	spin_unlock_bh(&ap->sta_table_lock);
    }
    
    
    #ifndef PRISM2_NO_KERNEL_IEEE80211_MGMT
    
    #define PROC_LIMIT (PAGE_SIZE - 80)
    
    static int prism2_ap_proc_read(char *page, char **start, off_t off,
    			       int count, int *eof, void *data)
    {
    	char *p = page;
    	struct ap_data *ap = (struct ap_data *) data;
    
    	struct sta_info *sta;
    
    	int i;
    
    	if (off > PROC_LIMIT) {
    		*eof = 1;
    		return 0;
    	}
    
    	p += sprintf(p, "# BSSID CHAN SIGNAL NOISE RATE SSID FLAGS\n");
    	spin_lock_bh(&ap->sta_table_lock);
    
    	list_for_each_entry(sta, &ap->sta_list, list) {
    
    		if (!sta->ap)
    			continue;
    
    
    		p += sprintf(p, "%pM %d %d %d %d '",
    			     sta->addr,
    
    			     sta->u.ap.channel, sta->last_rx_signal,
    			     sta->last_rx_silence, sta->last_rx_rate);
    		for (i = 0; i < sta->u.ap.ssid_len; i++)
    			p += sprintf(p, ((sta->u.ap.ssid[i] >= 32 &&
    					  sta->u.ap.ssid[i] < 127) ?
    					 "%c" : "<%02x>"),
    				     sta->u.ap.ssid[i]);
    		p += sprintf(p, "'");
    		if (sta->capability & WLAN_CAPABILITY_ESS)
    			p += sprintf(p, " [ESS]");
    		if (sta->capability & WLAN_CAPABILITY_IBSS)
    			p += sprintf(p, " [IBSS]");
    		if (sta->capability & WLAN_CAPABILITY_PRIVACY)
    			p += sprintf(p, " [WEP]");
    		p += sprintf(p, "\n");
    
    		if ((p - page) > PROC_LIMIT) {
    			printk(KERN_DEBUG "hostap: ap proc did not fit\n");
    			break;
    		}
    	}
    	spin_unlock_bh(&ap->sta_table_lock);
    
    	if ((p - page) <= off) {
    		*eof = 1;
    		return 0;
    	}
    
    	*start = page + off;
    
    	return (p - page - off);
    }
    #endif /* PRISM2_NO_KERNEL_IEEE80211_MGMT */
    
    
    void hostap_check_sta_fw_version(struct ap_data *ap, int sta_fw_ver)
    {
    	if (!ap)
    		return;
    
    	if (sta_fw_ver == PRISM2_FW_VER(0,8,0)) {
    		PDEBUG(DEBUG_AP, "Using data::nullfunc ACK workaround - "
    		       "firmware upgrade recommended\n");
    		ap->nullfunc_ack = 1;
    	} else
    		ap->nullfunc_ack = 0;
    
    	if (sta_fw_ver == PRISM2_FW_VER(1,4,2)) {
    		printk(KERN_WARNING "%s: Warning: secondary station firmware "
    		       "version 1.4.2 does not seem to work in Host AP mode\n",
    		       ap->local->dev->name);
    	}
    }
    
    
    /* Called only as a tasklet (software IRQ) */
    static void hostap_ap_tx_cb(struct sk_buff *skb, int ok, void *data)
    {
    	struct ap_data *ap = data;
    
    
    	if (!ap->local->hostapd || !ap->local->apdev) {
    		dev_kfree_skb(skb);
    		return;
    	}
    
    	/* Pass the TX callback frame to the hostapd; use 802.11 header version
    	 * 1 to indicate failure (no ACK) and 2 success (frame ACKed) */
    
    
    	hdr = (struct ieee80211_hdr *) skb->data;
    	hdr->frame_control &= cpu_to_le16(~IEEE80211_FCTL_VERS);
    	hdr->frame_control |= cpu_to_le16(ok ? BIT(1) : BIT(0));
    
    
    	skb->dev = ap->local->apdev;
    
    	skb_pull(skb, hostap_80211_get_hdrlen(hdr->frame_control));
    
    	skb->pkt_type = PACKET_OTHERHOST;
    
    	skb->protocol = cpu_to_be16(ETH_P_802_2);
    
    	memset(skb->cb, 0, sizeof(skb->cb));
    	netif_rx(skb);
    }
    
    
    #ifndef PRISM2_NO_KERNEL_IEEE80211_MGMT
    /* Called only as a tasklet (software IRQ) */
    static void hostap_ap_tx_cb_auth(struct sk_buff *skb, int ok, void *data)
    {
    	struct ap_data *ap = data;
    	struct net_device *dev = ap->local->dev;
    
    	struct ieee80211_hdr *hdr;
    	u16 auth_alg, auth_transaction, status;
    
    Al Viro's avatar
    Al Viro committed
    	__le16 *pos;
    
    	struct sta_info *sta = NULL;
    	char *txt = NULL;
    
    	if (ap->local->hostapd) {
    		dev_kfree_skb(skb);
    		return;
    	}
    
    
    	hdr = (struct ieee80211_hdr *) skb->data;
    	if (!ieee80211_is_auth(hdr->frame_control) ||
    
    	    skb->len < IEEE80211_MGMT_HDR_LEN + 6) {
    		printk(KERN_DEBUG "%s: hostap_ap_tx_cb_auth received invalid "
    		       "frame\n", dev->name);
    		dev_kfree_skb(skb);
    		return;
    	}
    
    
    Al Viro's avatar
    Al Viro committed
    	pos = (__le16 *) (skb->data + IEEE80211_MGMT_HDR_LEN);
    
    	auth_alg = le16_to_cpu(*pos++);
    	auth_transaction = le16_to_cpu(*pos++);
    	status = le16_to_cpu(*pos++);
    
    	if (!ok) {
    		txt = "frame was not ACKed";
    		goto done;
    	}
    
    	spin_lock(&ap->sta_table_lock);
    	sta = ap_get_sta(ap, hdr->addr1);
    	if (sta)
    		atomic_inc(&sta->users);
    	spin_unlock(&ap->sta_table_lock);
    
    	if (!sta) {
    		txt = "STA not found";
    		goto done;
    	}
    
    	if (status == WLAN_STATUS_SUCCESS &&
    	    ((auth_alg == WLAN_AUTH_OPEN && auth_transaction == 2) ||
    	     (auth_alg == WLAN_AUTH_SHARED_KEY && auth_transaction == 4))) {
    		txt = "STA authenticated";
    		sta->flags |= WLAN_STA_AUTH;
    		sta->last_auth = jiffies;
    	} else if (status != WLAN_STATUS_SUCCESS)
    		txt = "authentication failed";
    
     done:
    	if (sta)
    		atomic_dec(&sta->users);
    	if (txt) {
    
    		PDEBUG(DEBUG_AP, "%s: %pM auth_cb - alg=%d "
    
    		       "trans#=%d status=%d - %s\n",
    
    		       dev->name, hdr->addr1,
    
    		       auth_alg, auth_transaction, status, txt);
    
    	}
    	dev_kfree_skb(skb);
    }
    
    
    /* Called only as a tasklet (software IRQ) */
    static void hostap_ap_tx_cb_assoc(struct sk_buff *skb, int ok, void *data)
    {
    	struct ap_data *ap = data;
    	struct net_device *dev = ap->local->dev;
    
    Al Viro's avatar
    Al Viro committed
    	__le16 *pos;
    
    	struct sta_info *sta = NULL;
    	char *txt = NULL;
    
    	if (ap->local->hostapd) {
    		dev_kfree_skb(skb);
    		return;
    	}
    
    
    	hdr = (struct ieee80211_hdr *) skb->data;
    	if ((!ieee80211_is_assoc_resp(hdr->frame_control) &&
    	     !ieee80211_is_reassoc_resp(hdr->frame_control)) ||
    
    	    skb->len < IEEE80211_MGMT_HDR_LEN + 4) {
    		printk(KERN_DEBUG "%s: hostap_ap_tx_cb_assoc received invalid "
    		       "frame\n", dev->name);
    		dev_kfree_skb(skb);
    		return;
    	}
    
    	if (!ok) {
    		txt = "frame was not ACKed";
    		goto done;
    	}
    
    	spin_lock(&ap->sta_table_lock);
    	sta = ap_get_sta(ap, hdr->addr1);
    	if (sta)
    		atomic_inc(&sta->users);
    	spin_unlock(&ap->sta_table_lock);
    
    	if (!sta) {
    		txt = "STA not found";
    		goto done;
    	}
    
    
    Al Viro's avatar
    Al Viro committed
    	pos = (__le16 *) (skb->data + IEEE80211_MGMT_HDR_LEN);
    
    	pos++;
    	status = le16_to_cpu(*pos++);
    	if (status == WLAN_STATUS_SUCCESS) {
    		if (!(sta->flags & WLAN_STA_ASSOC))
    			hostap_event_new_sta(dev, sta);
    		txt = "STA associated";
    		sta->flags |= WLAN_STA_ASSOC;
    		sta->last_assoc = jiffies;
    	} else
    		txt = "association failed";
    
     done:
    	if (sta)
    		atomic_dec(&sta->users);
    	if (txt) {
    
    		PDEBUG(DEBUG_AP, "%s: %pM assoc_cb - %s\n",
    		       dev->name, hdr->addr1, txt);
    
    	}
    	dev_kfree_skb(skb);
    }
    
    /* Called only as a tasklet (software IRQ); TX callback for poll frames used
     * in verifying whether the STA is still present. */
    static void hostap_ap_tx_cb_poll(struct sk_buff *skb, int ok, void *data)
    {
    	struct ap_data *ap = data;
    
    	struct sta_info *sta;
    
    	if (skb->len < 24)
    		goto fail;
    
    	hdr = (struct ieee80211_hdr *) skb->data;
    
    	if (ok) {
    		spin_lock(&ap->sta_table_lock);
    		sta = ap_get_sta(ap, hdr->addr1);
    		if (sta)
    			sta->flags &= ~WLAN_STA_PENDING_POLL;
    		spin_unlock(&ap->sta_table_lock);
    	} else {
    
    		PDEBUG(DEBUG_AP,
    		       "%s: STA %pM did not ACK activity poll frame\n",
    		       ap->local->dev->name, hdr->addr1);
    
    	}
    
     fail:
    	dev_kfree_skb(skb);
    }
    #endif /* PRISM2_NO_KERNEL_IEEE80211_MGMT */
    
    
    void hostap_init_data(local_info_t *local)
    {
    	struct ap_data *ap = local->ap;
    
    	if (ap == NULL) {
    		printk(KERN_WARNING "hostap_init_data: ap == NULL\n");
    		return;
    	}
    	memset(ap, 0, sizeof(struct ap_data));
    	ap->local = local;
    
    	ap->ap_policy = GET_INT_PARM(other_ap_policy, local->card_idx);
    	ap->bridge_packets = GET_INT_PARM(ap_bridge_packets, local->card_idx);
    	ap->max_inactivity =
    		GET_INT_PARM(ap_max_inactivity, local->card_idx) * HZ;
    	ap->autom_ap_wds = GET_INT_PARM(autom_ap_wds, local->card_idx);
    
    	spin_lock_init(&ap->sta_table_lock);
    	INIT_LIST_HEAD(&ap->sta_list);
    
    	/* Initialize task queue structure for AP management */
    
    	INIT_WORK(&local->ap->add_sta_proc_queue, handle_add_proc_queue);
    
    
    	ap->tx_callback_idx =
    		hostap_tx_callback_register(local, hostap_ap_tx_cb, ap);
    	if (ap->tx_callback_idx == 0)
    		printk(KERN_WARNING "%s: failed to register TX callback for "
    		       "AP\n", local->dev->name);
    #ifndef PRISM2_NO_KERNEL_IEEE80211_MGMT
    
    	INIT_WORK(&local->ap->wds_oper_queue, handle_wds_oper_queue);
    
    
    	ap->tx_callback_auth =
    		hostap_tx_callback_register(local, hostap_ap_tx_cb_auth, ap);
    	ap->tx_callback_assoc =
    		hostap_tx_callback_register(local, hostap_ap_tx_cb_assoc, ap);
    	ap->tx_callback_poll =
    		hostap_tx_callback_register(local, hostap_ap_tx_cb_poll, ap);
    	if (ap->tx_callback_auth == 0 || ap->tx_callback_assoc == 0 ||
    		ap->tx_callback_poll == 0)
    		printk(KERN_WARNING "%s: failed to register TX callback for "
    		       "AP\n", local->dev->name);
    
    	spin_lock_init(&ap->mac_restrictions.lock);
    	INIT_LIST_HEAD(&ap->mac_restrictions.mac_list);
    #endif /* PRISM2_NO_KERNEL_IEEE80211_MGMT */
    
    	ap->initialized = 1;
    }
    
    
    void hostap_init_ap_proc(local_info_t *local)
    {
    	struct ap_data *ap = local->ap;
    
    	ap->proc = local->proc;
    	if (ap->proc == NULL)
    		return;
    
    #ifndef PRISM2_NO_PROCFS_DEBUG
    	create_proc_read_entry("ap_debug", 0, ap->proc,
    			       ap_debug_proc_read, ap);
    #endif /* PRISM2_NO_PROCFS_DEBUG */
    
    #ifndef PRISM2_NO_KERNEL_IEEE80211_MGMT
    	create_proc_read_entry("ap_control", 0, ap->proc,
    			       ap_control_proc_read, ap);
    	create_proc_read_entry("ap", 0, ap->proc,
    			       prism2_ap_proc_read, ap);
    #endif /* PRISM2_NO_KERNEL_IEEE80211_MGMT */
    
    }
    
    
    void hostap_free_data(struct ap_data *ap)
    {
    
    	struct sta_info *n, *sta;
    
    
    	if (ap == NULL || !ap->initialized) {
    		printk(KERN_DEBUG "hostap_free_data: ap has not yet been "
    		       "initialized - skip resource freeing\n");
    		return;
    	}
    
    
    	flush_work(&ap->add_sta_proc_queue);
    
    #ifndef PRISM2_NO_KERNEL_IEEE80211_MGMT
    
    	flush_work(&ap->wds_oper_queue);
    
    	if (ap->crypt)
    		ap->crypt->deinit(ap->crypt_priv);
    	ap->crypt = ap->crypt_priv = NULL;
    #endif /* PRISM2_NO_KERNEL_IEEE80211_MGMT */
    
    
    	list_for_each_entry_safe(sta, n, &ap->sta_list, list) {
    
    		ap_sta_hash_del(ap, sta);
    		list_del(&sta->list);
    		if ((sta->flags & WLAN_STA_ASSOC) && !sta->ap && sta->local)
    			hostap_event_expired_sta(sta->local->dev, sta);
    		ap_free_sta(ap, sta);
    	}
    
    #ifndef PRISM2_NO_PROCFS_DEBUG
    	if (ap->proc != NULL) {
    		remove_proc_entry("ap_debug", ap->proc);
    	}
    #endif /* PRISM2_NO_PROCFS_DEBUG */
    
    #ifndef PRISM2_NO_KERNEL_IEEE80211_MGMT
    	if (ap->proc != NULL) {
    	  remove_proc_entry("ap", ap->proc);
    		remove_proc_entry("ap_control", ap->proc);
    	}
    	ap_control_flush_macs(&ap->mac_restrictions);
    #endif /* PRISM2_NO_KERNEL_IEEE80211_MGMT */
    
    	ap->initialized = 0;
    }
    
    
    /* caller should have mutex for AP STA list handling */
    static struct sta_info* ap_get_sta(struct ap_data *ap, u8 *sta)
    {
    	struct sta_info *s;
    
    	s = ap->sta_hash[STA_HASH(sta)];
    	while (s != NULL && memcmp(s->addr, sta, ETH_ALEN) != 0)
    		s = s->hnext;
    	return s;
    }
    
    
    #ifndef PRISM2_NO_KERNEL_IEEE80211_MGMT
    
    /* Called from timer handler and from scheduled AP queue handlers */
    static void prism2_send_mgmt(struct net_device *dev,
    
    			     u16 type_subtype, char *body,
    
    			     int body_len, u8 *addr, u16 tx_cb_idx)
    {
    	struct hostap_interface *iface;
    	local_info_t *local;
    
    	u16 fc;
    	struct sk_buff *skb;
    	struct hostap_skb_tx_data *meta;
    	int hdrlen;
    
    	iface = netdev_priv(dev);
    	local = iface->local;
    	dev = local->dev; /* always use master radio device */
    	iface = netdev_priv(dev);
    
    	if (!(dev->flags & IFF_UP)) {
    		PDEBUG(DEBUG_AP, "%s: prism2_send_mgmt - device is not UP - "
    		       "cannot send frame\n", dev->name);
    		return;
    	}
    
    	skb = dev_alloc_skb(sizeof(*hdr) + body_len);
    	if (skb == NULL) {
    		PDEBUG(DEBUG_AP, "%s: prism2_send_mgmt failed to allocate "
    		       "skb\n", dev->name);
    		return;
    	}
    
    
    	hdrlen = hostap_80211_get_hdrlen(cpu_to_le16(type_subtype));
    	hdr = (struct ieee80211_hdr *) skb_put(skb, hdrlen);
    
    	if (body)
    		memcpy(skb_put(skb, body_len), body, body_len);
    
    	memset(hdr, 0, hdrlen);
    
    	/* FIX: ctrl::ack sending used special HFA384X_TX_CTRL_802_11
    	 * tx_control instead of using local->tx_control */
    
    
    	memcpy(hdr->addr1, addr, ETH_ALEN); /* DA / RA */
    
    	if (ieee80211_is_data(hdr->frame_control)) {
    
    		fc |= IEEE80211_FCTL_FROMDS;
    
    		memcpy(hdr->addr2, dev->dev_addr, ETH_ALEN); /* BSSID */
    		memcpy(hdr->addr3, dev->dev_addr, ETH_ALEN); /* SA */
    
    	} else if (ieee80211_is_ctl(hdr->frame_control)) {
    
    		/* control:ACK does not have addr2 or addr3 */
    		memset(hdr->addr2, 0, ETH_ALEN);
    		memset(hdr->addr3, 0, ETH_ALEN);
    	} else {
    		memcpy(hdr->addr2, dev->dev_addr, ETH_ALEN); /* SA */
    		memcpy(hdr->addr3, dev->dev_addr, ETH_ALEN); /* BSSID */
    	}
    
    
    	hdr->frame_control = cpu_to_le16(fc);
    
    
    	meta = (struct hostap_skb_tx_data *) skb->cb;
    	memset(meta, 0, sizeof(*meta));
    	meta->magic = HOSTAP_SKB_TX_DATA_MAGIC;
    	meta->iface = iface;
    	meta->tx_cb_idx = tx_cb_idx;
    
    	skb->dev = dev;
    
    	skb_reset_mac_header(skb);
    
    	skb_reset_network_header(skb);
    
    	dev_queue_xmit(skb);
    }
    #endif /* PRISM2_NO_KERNEL_IEEE80211_MGMT */
    
    
    static int prism2_sta_proc_read(char *page, char **start, off_t off,
    				int count, int *eof, void *data)
    {
    	char *p = page;
    	struct sta_info *sta = (struct sta_info *) data;
    	int i;
    
    	/* FIX: possible race condition.. the STA data could have just expired,
    	 * but proc entry was still here so that the read could have started;
    	 * some locking should be done here.. */
    
    	if (off != 0) {
    		*eof = 1;
    		return 0;
    	}