blob: 1be8c645bc14536aab501e42f8f4ef9b680f0955 [file] [log] [blame]
/*-
* Copyright (c) 2003-2005 Sam Leffler, Errno Consulting
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* $Id: ieee80211_linux.c 2759 2007-10-17 21:48:20Z kelmo $
*/
#ifndef EXPORT_SYMTAB
#define EXPORT_SYMTAB
#endif
/*
* IEEE 802.11 support (Linux-specific code)
*/
#ifndef AUTOCONF_INCLUDED
#include <linux/config.h>
#endif
#include <linux/version.h>
#include <linux/module.h>
#include <linux/kmod.h>
#include <linux/init.h>
#include <linux/skbuff.h>
#include <linux/sysctl.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/if_vlan.h>
#include <linux/vmalloc.h>
#include <linux/proc_fs.h>
#include <net/iw_handler.h>
#include <linux/wireless.h>
#include <linux/if_arp.h> /* XXX for ARPHRD_* */
#include <asm/uaccess.h>
#include "qtn/qtn_global.h"
#include "net80211/if_media.h"
#include "net80211/if_ethersubr.h"
#include "net80211/ieee80211_var.h"
#include "net80211/ieee80211_monitor.h"
#define proc_net init_net.proc_net
/*
* Print a console message with the device name prepended.
*/
void
if_printf(struct net_device *dev, const char *fmt, ...)
{
va_list ap;
char buf[512]; /* XXX */
va_start(ap, fmt);
vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
printk("%s: %s", dev->name, buf);
}
/*
* Allocate a data frame
* Returns the sk_buff and a pointer to the start of the reserved contiguous data area.
*/
struct sk_buff *
ieee80211_getdataframe(struct ieee80211vap *vap, uint8_t **frm, uint8_t qos, uint32_t payload_len)
{
struct sk_buff *skb;
uint32_t hdrlen;
if (qos)
hdrlen = sizeof(struct ieee80211_qosframe);
else
hdrlen = sizeof(struct ieee80211_frame);
skb = dev_alloc_skb(hdrlen + payload_len);
if (!skb) {
IEEE80211_DPRINTF(vap, IEEE80211_MSG_ANY,
"%s: cannot get buf of size %u", __func__,
hdrlen + payload_len);
vap->iv_stats.is_tx_nobuf++;
return NULL;
}
skb_reserve(skb, hdrlen);
*frm = skb_put(skb, payload_len);
return skb;
}
EXPORT_SYMBOL(ieee80211_getmgtframe);
/*
* Allocate a management frame
* Returns the sk_buff and a pointer to the start of the reserved contiguous data area.
* The data area is forced to 32-bit alignment and the buffer length to a multiple of 4 bytes. This
* is done mainly so beacon frames (that require this) can use this interface too.
*/
struct sk_buff *
ieee80211_getmgtframe(uint8_t **frm, uint32_t payload_len)
{
struct sk_buff *skb;
uint32_t alignment, len;
len = roundup(sizeof(struct ieee80211_frame) + payload_len, 4);
skb = dev_alloc_skb(len + ARC_DCACHE_LINE_LEN - 1);
if (skb != NULL) {
/* Cache align the frame */
alignment = (unsigned int)(skb->data) & (ARC_DCACHE_LINE_LEN - 1);
if (alignment) {
skb_reserve(skb, ARC_DCACHE_LINE_LEN - alignment);
}
skb_reserve(skb, sizeof(struct ieee80211_frame));
*frm = skb_put(skb, payload_len);
}
return skb;
}
#if IEEE80211_VLAN_TAG_USED
/*
* VLAN support.
*/
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,7,0)
/*
* Register a vlan group.
*/
static void
ieee80211_vlan_register(struct net_device *dev, struct vlan_group *grp)
{
struct ieee80211vap *vap = netdev_priv(dev);
vap->iv_vlgrp = grp;
}
#endif
/*
* Add an rx vlan identifier
*/
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0)
static int
ieee80211_vlan_add_vid(struct net_device *dev, __be16 proto, u16 vid)
#else
static void
ieee80211_vlan_add_vid(struct net_device *dev, unsigned short vid)
#endif
{
struct ieee80211vap *vap = netdev_priv(dev);
if (vap->iv_vlgrp != NULL)
vap->iv_bss->ni_vlan = vid;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0)
return 0;
#endif
}
/*
* Kill (i.e. delete) a vlan identifier.
*/
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0)
static int
ieee80211_vlan_kill_vid(struct net_device *dev, __be16 proto, u16 vid)
#else
static void
ieee80211_vlan_kill_vid(struct net_device *dev, unsigned short vid)
#endif
{
struct ieee80211vap *vap = netdev_priv(dev);
if (vap->iv_vlgrp != NULL)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0)
vlan_group_set_device(vap->iv_vlgrp, proto, vid, NULL);
#else
vlan_group_set_device(vap->iv_vlgrp, vid, NULL);
#endif
return 0;
}
#endif /* IEEE80211_VLAN_TAG_USED */
void
ieee80211_vlan_vattach(struct ieee80211vap *vap)
{
#if IEEE80211_VLAN_TAG_USED
struct net_device *dev = vap->iv_dev;
struct net_device_ops *pndo = (struct net_device_ops *)dev->netdev_ops;
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,7,0)
dev->features |= NETIF_F_HW_VLAN_TX | NETIF_F_HW_VLAN_RX |
NETIF_F_HW_VLAN_FILTER;
#else
dev->features |= NETIF_F_HW_VLAN_CTAG_TX | NETIF_F_HW_VLAN_CTAG_RX |
NETIF_F_HW_VLAN_CTAG_FILTER;
#endif
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,14,24)
pndo->ndo_vlan_rx_register = ieee80211_vlan_register;
#endif
pndo->ndo_vlan_rx_add_vid = ieee80211_vlan_add_vid;
pndo->ndo_vlan_rx_kill_vid = ieee80211_vlan_kill_vid;
#endif /* IEEE80211_VLAN_TAG_USED */
}
void
ieee80211_vlan_vdetach(struct ieee80211vap *vap)
{
}
void
ieee80211_notify_node_join(struct ieee80211_node *ni, int newassoc)
{
struct ieee80211vap *vap = ni->ni_vap;
struct net_device *dev = vap->iv_dev;
union iwreq_data wreq;
if (ni == vap->iv_bss) {
if (newassoc) {
netif_carrier_on(dev);
}
memset(&wreq, 0, sizeof(wreq));
IEEE80211_ADDR_COPY(wreq.addr.sa_data, ni->ni_bssid);
wreq.addr.sa_family = ARPHRD_ETHER;
wireless_send_event(dev, SIOCGIWAP, &wreq, NULL);
ieee80211_extender_notify_ext_role(ni);
} else {
memset(&wreq, 0, sizeof(wreq));
IEEE80211_ADDR_COPY(wreq.addr.sa_data, ni->ni_macaddr);
wreq.addr.sa_family = ARPHRD_ETHER;
wireless_send_event(dev, IWEVREGISTERED, &wreq, NULL);
}
}
void
ieee80211_notify_node_leave(struct ieee80211_node *ni)
{
struct ieee80211vap *vap = ni->ni_vap;
struct net_device *dev = vap->iv_dev;
union iwreq_data wreq;
if (ni == vap->iv_bss) {
memset(wreq.ap_addr.sa_data, 0, ETHER_ADDR_LEN);
wreq.ap_addr.sa_family = ARPHRD_ETHER;
wireless_send_event(dev, SIOCGIWAP, &wreq, NULL);
} else {
/* fire off wireless event station leaving */
memset(&wreq, 0, sizeof(wreq));
IEEE80211_ADDR_COPY(wreq.addr.sa_data, ni->ni_macaddr);
wreq.addr.sa_family = ARPHRD_ETHER;
wireless_send_event(dev, IWEVEXPIRED, &wreq, NULL);
}
}
void
ieee80211_notify_sta_stats(struct ieee80211_node *ni)
{
struct ieee80211vap *vap = ni->ni_vap;
static const char *tag = "STA-TRAFFIC-STAT";
struct net_device *dev = vap->iv_dev;
ieee80211_eventf(dev, "%s\nmac=%s\nrx_packets=%u\nrx_bytes=%llu\n"
"tx_packets=%u\ntx_bytes=%llu\n", tag,
ether_sprintf(ni->ni_macaddr), ni->ni_stats.ns_rx_data,
ni->ni_stats.ns_rx_bytes, ni->ni_stats.ns_tx_data,
ni->ni_stats.ns_tx_bytes);
}
void
ieee80211_notify_scan_done(struct ieee80211vap *vap)
{
struct net_device *dev = vap->iv_dev;
union iwreq_data wreq;
IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s\n", "notify scan done");
/* dispatch wireless event indicating scan completed */
wreq.data.length = 0;
wreq.data.flags = 0;
wireless_send_event(dev, SIOCGIWSCAN, &wreq, NULL);
}
void
ieee80211_notify_replay_failure(struct ieee80211vap *vap,
const struct ieee80211_frame *wh, const struct ieee80211_key *k,
u_int64_t rsc)
{
static const char *tag = "MLME-REPLAYFAILURE.indication";
struct net_device *dev = vap->iv_dev;
IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_CRYPTO, wh->i_addr2,
"%s replay detected <keyix %d, rsc %llu >",
k->wk_cipher->ic_name, k->wk_keyix, rsc );
/* TODO: needed parameters: count, keyid, key type, src address, TSC */
ieee80211_eventf(dev, "%s(keyid=%d %scast addr=%s)", tag,
k->wk_keyix,
IEEE80211_IS_MULTICAST(wh->i_addr1) ? "broad" : "uni",
ether_sprintf(wh->i_addr1));
}
EXPORT_SYMBOL(ieee80211_notify_replay_failure);
void
ieee80211_nofity_sta_require_leave(struct ieee80211_node *ni)
{
struct ieee80211vap *vap = ni->ni_vap;
struct net_device *dev = vap->iv_dev;
const char *indicator = "STA-REQUIRE-LEAVE";
if (vap->iv_opmode == IEEE80211_M_HOSTAP && ni->ni_associd != 0) {
ieee80211_eventf(dev, "%s=%s", indicator, ether_sprintf(ni->ni_macaddr));
}
}
/**
* Entry point for reporting of MIC failure (via ic pointer).
*/
void ieee80211_tkip_mic_failure(struct ieee80211vap *vap, int count)
{
int i;
/* If more than two errors, only report the first two as this
* will be enough to trigger countermeasures.
*/
if (count > 2)
count = 2;
/* Send up to 2 reports */
for (i = 0; i < count; i++)
{
/* Format a frame header appropriately for a MIC report */
struct ieee80211_frame wh;
memset(&wh, 0, sizeof(wh));
memcpy(&wh.i_addr1, vap->iv_bss->ni_macaddr, IEEE80211_ADDR_LEN);
memcpy(&wh.i_addr2, vap->iv_bss->ni_bssid, IEEE80211_ADDR_LEN);
ieee80211_notify_michael_failure(vap, &wh, 0);
}
}
void
ieee80211_notify_michael_failure(struct ieee80211vap *vap,
const struct ieee80211_frame *wh, u_int keyix)
{
static const char *tag = "MLME-MICHAELMICFAILURE.indication";
struct net_device *dev = vap->iv_dev;
IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_CRYPTO, wh->i_addr2,
"Michael MIC verification failed <keyix %d>", keyix);
vap->iv_stats.is_rx_tkipmic++;
/* TODO: needed parameters: count, keyid, key type, src address, TSC */
/* qtn=1 is used (in conjunction with userspace hostapd change) to ensure that the
* MIC failure report is tallied, regardless of the source address.
*/
ieee80211_eventf(dev, "%s(keyid=%d %scast addr=%s qtn=1)", tag,
keyix, IEEE80211_IS_MULTICAST(wh->i_addr1) ? "broad" : "uni",
ether_sprintf(wh->i_addr1));
}
EXPORT_SYMBOL(ieee80211_notify_michael_failure);
/*
* Note that a successful call to this function does not guarantee that
* the services provided by the requested module are available:
*
* "Note that a successful module load does not mean the module did not
* then unload and exit on an error of its own. Callers must check that
* the service they requested is now available not blindly invoke it."
* http://kernelnewbies.org/documents/kdoc/kernel-api/r7338.html
*/
int
ieee80211_load_module(const char *modname)
{
#if defined(CONFIG_KMOD) || defined(CONFIG_MODULES)
int rv;
rv = request_module(modname);
if (rv < 0)
printk(KERN_ERR "failed to automatically load module: %s; " \
"errno: %d\n", modname, rv);
return rv;
#else /* CONFIG_KMOD || CONFIG_MODULES */
printk(KERN_ERR "Unable to load needed module: %s; no support for " \
"automatic module loading", modname );
return -ENOSYS;
#endif /* CONFIG_KMOD || CONFIG_MODULES */
}
static struct proc_dir_entry *proc_madwifi;
static int proc_madwifi_count = 0;
/**
* Return a string representing the MIMO power save mode passed in.
*/
static char *
ieee80211_smps_to_string(u_int8_t pwrsave)
{
switch(pwrsave)
{
case IEEE80211_HTCAP_C_MIMOPWRSAVE_STATIC:
return "Static";
break;
case IEEE80211_HTCAP_C_MIMOPWRSAVE_DYNAMIC:
return "Dynamic";
break;
case IEEE80211_HTCAP_C_MIMOPWRSAVE_NA:
return "INVALID";
break;
case IEEE80211_HTCAP_C_MIMOPWRSAVE_NONE:
return "None";
break;
default:
return "Unknown";
break;
}
return "Unknown";
}
/**
* Return a STATIC buffer containing a dump of the HT capability info field
*/
static char *
ieee80211_htcapinfo_to_string(struct ieee80211vap *vap, struct ieee80211_htcap *ni_htcap)
{
static char htcapinfobuf[1024];
char *p = htcapinfobuf;
int printed = 0;
printed += snprintf(p, sizeof(htcapinfobuf), "MIMO power save:%s",
ieee80211_smps_to_string(ni_htcap->pwrsave));
p += printed;
if (vap->iv_smps_force & 0x8000)
{
snprintf(p, sizeof(htcapinfobuf) - printed, " Overridden to:%s (%04X)",
ieee80211_smps_to_string((u_int8_t)(vap->iv_smps_force & 0xFF)), vap->iv_smps_force);
}
/* FIXME: decode other parts of the capability IE here */
return htcapinfobuf;
}
/* Check whether to output a node entry for the /proc/net/madwifi/wifi0/associated_sta output */
static int
ieee80211_node_should_print(struct ieee80211vap *vap, struct ieee80211_node *ni)
{
if ((ni->ni_vap == vap) &&
(0 != memcmp(vap->iv_myaddr, ni->ni_macaddr, IEEE80211_ADDR_LEN)) &&
(ni->ni_associd) &&
(ni->ni_blacklist_timeout == 0) &&
ieee80211_node_is_authorized(ni)) {
return 1;
}
return 0;
}
static int
proc_read_nodes(struct ieee80211vap *vap, char *buf, int space)
{
char *p = buf;
struct ieee80211_node *ni;
struct ieee80211_node_table *nt = (struct ieee80211_node_table *) &vap->iv_ic->ic_sta;
/* Don't print anything out on the STA side if we're not connected. */
if ((vap->iv_opmode != IEEE80211_M_HOSTAP) &&
(vap->iv_state != IEEE80211_S_RUN))
{
return 0;
}
//IEEE80211_NODE_LOCK(nt);
TAILQ_FOREACH(ni, &nt->nt_node, ni_list) {
/* Assume each node needs 500 bytes */
if (buf + space < p + 500)
break;
/* Nodes associated with OUR VAP, not with OUR MAC address, and with
* NON-ZERO association ID (ie no temporary nodes)
*/
if (ieee80211_node_should_print(vap, ni)) {
struct timespec t;
jiffies_to_timespec(jiffies - ni->ni_last_rx, &t);
p += sprintf(p, "macaddr: <%s>\n", ether_sprintf(ni->ni_macaddr));
p += sprintf(p, " rssi %d\n", ni->ni_rssi);
p += sprintf(p, " last_rx %ld.%06ld %d\n",
t.tv_sec, t.tv_nsec / 1000, ni->ni_inact);
p += sprintf(p, " HT CAP: %s\n",
ieee80211_htcapinfo_to_string(vap, &ni->ni_htcap));
}
}
//IEEE80211_NODE_UNLOCK(nt);
return (p - buf);
}
static ssize_t
proc_ieee80211_read(struct file *file, char __user *buf, size_t len, loff_t *offset)
{
loff_t pos = *offset;
struct proc_ieee80211_priv *pv = (struct proc_ieee80211_priv *) file->private_data;
if (!pv->rbuf)
return -EINVAL;
if (pos < 0)
return -EINVAL;
if (pos > pv->rlen)
return -EFAULT;
if (len > pv->rlen - pos)
len = pv->rlen - pos;
if (copy_to_user(buf, pv->rbuf + pos, len))
return -EFAULT;
*offset = pos + len;
return len;
}
static int
proc_ieee80211_open(struct inode *inode, struct file *file)
{
struct proc_ieee80211_priv *pv = NULL;
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,7,0)
struct proc_dir_entry *dp = PDE(inode);
struct ieee80211vap *vap = dp->data;
#else
struct ieee80211vap *vap = PDE_DATA(inode);
#endif
if (!(file->private_data = kmalloc(sizeof(struct proc_ieee80211_priv), GFP_KERNEL)))
return -ENOMEM;
/* initially allocate both read and write buffers */
pv = (struct proc_ieee80211_priv *) file->private_data;
memset(pv, 0, sizeof(struct proc_ieee80211_priv));
pv->rbuf = vmalloc(MAX_PROC_IEEE80211_SIZE);
if (!pv->rbuf) {
kfree(pv);
return -ENOMEM;
}
pv->wbuf = vmalloc(MAX_PROC_IEEE80211_SIZE);
if (!pv->wbuf) {
vfree(pv->rbuf);
kfree(pv);
return -ENOMEM;
}
memset(pv->wbuf, 0, MAX_PROC_IEEE80211_SIZE);
memset(pv->rbuf, 0, MAX_PROC_IEEE80211_SIZE);
pv->max_wlen = MAX_PROC_IEEE80211_SIZE;
pv->max_rlen = MAX_PROC_IEEE80211_SIZE;
/* now read the data into the buffer */
pv->rlen = proc_read_nodes(vap, pv->rbuf, MAX_PROC_IEEE80211_SIZE);
return 0;
}
static ssize_t
proc_ieee80211_write(struct file *file, const char __user *buf, size_t len, loff_t *offset)
{
loff_t pos = *offset;
struct proc_ieee80211_priv *pv =
(struct proc_ieee80211_priv *) file->private_data;
if (!pv->wbuf)
return -EINVAL;
if (pos < 0)
return -EINVAL;
if (pos >= pv->max_wlen)
return 0;
if (len > pv->max_wlen - pos)
len = pv->max_wlen - pos;
if (copy_from_user(pv->wbuf + pos, buf, len))
return -EFAULT;
if (pos + len > pv->wlen)
pv->wlen = pos + len;
*offset = pos + len;
return len;
}
static int
proc_ieee80211_close(struct inode *inode, struct file *file)
{
struct proc_ieee80211_priv *pv =
(struct proc_ieee80211_priv *) file->private_data;
if (pv->rbuf)
vfree(pv->rbuf);
if (pv->wbuf)
vfree(pv->wbuf);
kfree(pv);
return 0;
}
static struct file_operations proc_ieee80211_ops = {
.read = proc_ieee80211_read,
.write = proc_ieee80211_write,
.open = proc_ieee80211_open,
.release = proc_ieee80211_close,
};
#ifdef IEEE80211_DEBUG
static int
IEEE80211_SYSCTL_DECL(ieee80211_sysctl_debug, ctl, write, filp, buffer,
lenp, ppos)
{
struct ieee80211vap *vap = ctl->extra1;
u_int val;
int ret;
ctl->data = &val;
ctl->maxlen = sizeof(val);
if (write) {
ret = IEEE80211_SYSCTL_PROC_DOINTVEC(ctl, write, filp, buffer,
lenp, ppos);
if (ret == 0)
vap->iv_debug = val;
} else {
val = vap->iv_debug;
ret = IEEE80211_SYSCTL_PROC_DOINTVEC(ctl, write, filp, buffer,
lenp, ppos);
}
return ret;
}
#endif /* IEEE80211_DEBUG */
static int
IEEE80211_SYSCTL_DECL(ieee80211_sysctl_dev_type, ctl, write, filp, buffer,
lenp, ppos)
{
struct ieee80211vap *vap = ctl->extra1;
u_int val;
int ret;
ctl->data = &val;
ctl->maxlen = sizeof(val);
if (write) {
ret = IEEE80211_SYSCTL_PROC_DOINTVEC(ctl, write, filp, buffer,
lenp, ppos);
if (ret == 0 && vap->iv_opmode == IEEE80211_M_MONITOR) {
if (val == ARPHRD_IEEE80211_RADIOTAP ||
val == ARPHRD_IEEE80211 ||
val == ARPHRD_IEEE80211_PRISM ||
val == ARPHRD_IEEE80211_ATHDESC) {
vap->iv_dev->type = val;
}
}
} else {
val = vap->iv_dev->type;
ret = IEEE80211_SYSCTL_PROC_DOINTVEC(ctl, write, filp, buffer,
lenp, ppos);
}
return ret;
}
static int
IEEE80211_SYSCTL_DECL(ieee80211_sysctl_monitor_nods_only, ctl, write, filp, buffer,
lenp, ppos)
{
struct ieee80211vap *vap = ctl->extra1;
u_int val;
int ret;
ctl->data = &val;
ctl->maxlen = sizeof(val);
if (write) {
ret = IEEE80211_SYSCTL_PROC_DOINTVEC(ctl, write, filp, buffer,
lenp, ppos);
if (ret == 0)
vap->iv_monitor_nods_only = val;
} else {
val = vap->iv_monitor_nods_only;
ret = IEEE80211_SYSCTL_PROC_DOINTVEC(ctl, write, filp, buffer,
lenp, ppos);
}
return ret;
}
static int
IEEE80211_SYSCTL_DECL(ieee80211_sysctl_monitor_txf_len, ctl, write, filp, buffer,
lenp, ppos)
{
struct ieee80211vap *vap = ctl->extra1;
u_int val;
int ret;
ctl->data = &val;
ctl->maxlen = sizeof(val);
if (write) {
ret = IEEE80211_SYSCTL_PROC_DOINTVEC(ctl, write, filp, buffer,
lenp, ppos);
if (ret == 0)
vap->iv_monitor_txf_len = val;
} else {
val = vap->iv_monitor_txf_len;
ret = IEEE80211_SYSCTL_PROC_DOINTVEC(ctl, write, filp, buffer,
lenp, ppos);
}
return ret;
}
static int
IEEE80211_SYSCTL_DECL(ieee80211_sysctl_monitor_phy_errors, ctl, write, filp, buffer,
lenp, ppos)
{
struct ieee80211vap *vap = ctl->extra1;
u_int val;
int ret;
ctl->data = &val;
ctl->maxlen = sizeof(val);
if (write) {
ret = IEEE80211_SYSCTL_PROC_DOINTVEC(ctl, write, filp, buffer,
lenp, ppos);
if (ret == 0)
vap->iv_monitor_phy_errors = val;
} else {
val = vap->iv_monitor_phy_errors;
ret = IEEE80211_SYSCTL_PROC_DOINTVEC(ctl, write, filp, buffer,
lenp, ppos);
}
return ret;
}
static int
IEEE80211_SYSCTL_DECL(ieee80211_sysctl_monitor_crc_errors, ctl, write, filp, buffer,
lenp, ppos)
{
struct ieee80211vap *vap = ctl->extra1;
u_int val;
int ret;
ctl->data = &val;
ctl->maxlen = sizeof(val);
if (write) {
ret = IEEE80211_SYSCTL_PROC_DOINTVEC(ctl, write, filp, buffer,
lenp, ppos);
if (ret == 0)
vap->iv_monitor_crc_errors = val;
} else {
val = vap->iv_monitor_crc_errors;
ret = IEEE80211_SYSCTL_PROC_DOINTVEC(ctl, write, filp, buffer,
lenp, ppos);
}
return ret;
}
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35)
#define CTL_AUTO -2 /* cannot be CTL_ANY or CTL_NONE */
#define INIT_CTL_NAME(value) .ctl_name = (value),
#else
#define INIT_CTL_NAME(value)
#endif
static const struct ctl_table ieee80211_sysctl_template[] = {
#ifdef IEEE80211_DEBUG
{
INIT_CTL_NAME(CTL_AUTO)
.procname = "debug",
.mode = 0644,
.proc_handler = ieee80211_sysctl_debug
},
#endif
{
INIT_CTL_NAME(CTL_AUTO)
.procname = "dev_type",
.mode = 0644,
.proc_handler = ieee80211_sysctl_dev_type
},
{
INIT_CTL_NAME(CTL_AUTO)
.procname = "monitor_nods_only",
.mode = 0644,
.proc_handler = ieee80211_sysctl_monitor_nods_only
},
{
INIT_CTL_NAME(CTL_AUTO)
.procname = "monitor_txf_len",
.mode = 0644,
.proc_handler = ieee80211_sysctl_monitor_txf_len
},
{ INIT_CTL_NAME(CTL_AUTO)
.procname = "monitor_phy_errors",
.mode = 0644,
.proc_handler = ieee80211_sysctl_monitor_phy_errors
},
{
INIT_CTL_NAME(CTL_AUTO)
.procname = "monitor_crc_errors",
.mode = 0644,
.proc_handler = ieee80211_sysctl_monitor_crc_errors
},
/* NB: must be last entry before NULL */
{
INIT_CTL_NAME(CTL_AUTO)
.procname = "%parent",
.maxlen = IFNAMSIZ,
.mode = 0444,
.proc_handler = proc_dostring
},
{ 0 }
};
void
ieee80211_sysctl_vattach(struct ieee80211vap *vap)
{
int i, space;
char *devname = NULL;
struct ieee80211_proc_entry *tmp=NULL;
space = 5 * sizeof(struct ctl_table) + sizeof(ieee80211_sysctl_template);
vap->iv_sysctls = kmalloc(space, GFP_KERNEL);
if (vap->iv_sysctls == NULL) {
printk("%s: no memory for sysctl table!\n", __func__);
return;
}
/*
* Reserve space for the device name outside the net_device structure
* so that if the name changes we know what it used to be.
*/
devname = kmalloc((strlen(vap->iv_dev->name) + 1) * sizeof(char), GFP_KERNEL);
if (devname == NULL) {
printk("%s: no memory for VAP name!\n", __func__);
kfree(vap->iv_sysctls);
vap->iv_sysctls = NULL;
return;
}
strncpy(devname, vap->iv_dev->name, strlen(vap->iv_dev->name) + 1);
/* setup the table */
memset(vap->iv_sysctls, 0, space);
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35)
vap->iv_sysctls[0].ctl_name = CTL_NET;
#endif
vap->iv_sysctls[0].procname = "net";
vap->iv_sysctls[0].mode = 0555;
vap->iv_sysctls[0].child = &vap->iv_sysctls[2];
/* [1] is NULL terminator */
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35)
vap->iv_sysctls[2].ctl_name = CTL_AUTO;
#endif
vap->iv_sysctls[2].procname = devname; /* XXX bad idea? */
vap->iv_sysctls[2].mode = 0555;
vap->iv_sysctls[2].child = &vap->iv_sysctls[4];
/* [3] is NULL terminator */
/* copy in pre-defined data */
memcpy(&vap->iv_sysctls[4], ieee80211_sysctl_template,
sizeof(ieee80211_sysctl_template));
/* add in dynamic data references */
for (i = 4; vap->iv_sysctls[i].procname; i++)
if (vap->iv_sysctls[i].extra1 == NULL)
vap->iv_sysctls[i].extra1 = vap;
vap->iv_sysctls[i-1].data = ""; /* XXX? */
/* and register everything */
vap->iv_sysctl_header = ATH_REGISTER_SYSCTL_TABLE(vap->iv_sysctls);
if (!vap->iv_sysctl_header) {
printk("%s: failed to register sysctls!\n", vap->iv_dev->name);
kfree(vap->iv_sysctls);
vap->iv_sysctls = NULL;
}
vap->iv_disconn_cnt = 0;
vap->iv_disconn_seq = 0;
/* Ensure the base madwifi directory exists */
if (!proc_madwifi && proc_net != NULL) {
proc_madwifi = proc_mkdir("madwifi", proc_net);
if (!proc_madwifi)
printk(KERN_WARNING "Failed to mkdir /proc/net/madwifi\n");
}
/* Create a proc directory named after the VAP */
if (proc_madwifi) {
proc_madwifi_count++;
vap->iv_proc = proc_mkdir(vap->iv_dev->name, proc_madwifi);
}
/* Create a proc entry listing the associated stations */
ieee80211_proc_vcreate(vap, &proc_ieee80211_ops, "associated_sta");
/* Recreate any other proc entries that have been registered */
if (vap->iv_proc) {
tmp = vap->iv_proc_entries;
while (tmp) {
if (!tmp->entry) {
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,7,0)
tmp->entry = create_proc_entry(tmp->name,
PROC_IEEE80211_PERM, vap->iv_proc);
tmp->entry->data = vap;
tmp->entry->proc_fops = tmp->fileops;
#else
tmp->entry = proc_create_data(tmp->name, PROC_IEEE80211_PERM,
vap->iv_proc, tmp->fileops, vap);
#endif
}
tmp = tmp->next;
}
}
}
/* Frees all memory used for the list of proc entries */
void
ieee80211_proc_cleanup(struct ieee80211vap *vap)
{
struct ieee80211_proc_entry *tmp=vap->iv_proc_entries;
struct ieee80211_proc_entry *next = NULL;
while (tmp) {
next = tmp->next;
kfree(tmp);
tmp = next;
}
}
/* Called by other modules to register a proc entry under the vap directory */
int
ieee80211_proc_vcreate(struct ieee80211vap *vap,
struct file_operations *fileops, char *name)
{
struct ieee80211_proc_entry *entry;
struct ieee80211_proc_entry *tmp=NULL;
/* Ignore if already in the list */
if (vap->iv_proc_entries) {
tmp = vap->iv_proc_entries;
do {
if (strcmp(tmp->name, name)==0)
return -1;
/* Check for end of list */
if (!tmp->next)
break;
/* Otherwise move on */
tmp = tmp->next;
} while (1);
}
/* Create an item in our list for the new entry */
entry = kmalloc(sizeof(struct ieee80211_proc_entry), GFP_KERNEL);
if (entry == NULL) {
printk("%s: no memory for new proc entry (%s)!\n", __func__,
name);
return -1;
}
/* Replace null fileops pointers with our standard functions */
if (!fileops->open)
fileops->open = proc_ieee80211_open;
if (!fileops->release)
fileops->release = proc_ieee80211_close;
if (!fileops->read)
fileops->read = proc_ieee80211_read;
if (!fileops->write)
fileops->write = proc_ieee80211_write;
/* Create the entry record */
entry->name = name;
entry->fileops = fileops;
entry->next = NULL;
entry->entry = NULL;
/* Create the actual proc entry */
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,7,0)
if (vap->iv_proc) {
entry->entry = create_proc_entry(entry->name,
PROC_IEEE80211_PERM, vap->iv_proc);
entry->entry->data = vap;
entry->entry->proc_fops = entry->fileops;
}
#else
if (vap->iv_proc)
entry->entry = proc_create_data(entry->name, PROC_IEEE80211_PERM,
vap->iv_proc, entry->fileops, vap);
#endif
/* Add it to the list */
if (!tmp) {
/* Add to the start */
vap->iv_proc_entries = entry;
} else {
/* Add to the end */
tmp->next = entry;
}
return 0;
}
EXPORT_SYMBOL(ieee80211_proc_vcreate);
void
ieee80211_sysctl_vdetach(struct ieee80211vap *vap)
{
struct ieee80211_proc_entry *tmp=NULL;
if (vap->iv_sysctl_header) {
unregister_sysctl_table(vap->iv_sysctl_header);
vap->iv_sysctl_header = NULL;
}
if (vap->iv_proc) {
/* Remove child proc entries but leave them in the list */
tmp = vap->iv_proc_entries;
while (tmp) {
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,7,0)
if (tmp->entry) {
remove_proc_entry(tmp->name, vap->iv_proc);
tmp->entry = NULL;
}
#else
tmp->entry = NULL;
#endif
tmp = tmp->next;
}
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,7,0)
remove_proc_entry(vap->iv_proc->name, proc_madwifi);
#else
proc_remove(vap->iv_proc);
#endif
if (proc_madwifi_count == 1) {
remove_proc_entry("madwifi", proc_net);
proc_madwifi = NULL;
}
proc_madwifi_count--;
}
if (vap->iv_sysctls) {
if (vap->iv_sysctls[2].procname) {
kfree(vap->iv_sysctls[2].procname);
vap->iv_sysctls[2].procname = NULL;
}
kfree(vap->iv_sysctls);
vap->iv_sysctls = NULL;
}
}
/*
* Format an Ethernet MAC for printing.
*/
const char*
ether_sprintf(const u_int8_t *mac)
{
static char etherbuf[18]; /* XXX */
snprintf(etherbuf, sizeof(etherbuf), "%02x:%02x:%02x:%02x:%02x:%02x",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
return etherbuf;
}
EXPORT_SYMBOL(ether_sprintf); /* XXX */
/* Function to handle the device event notifications.
* If the event is a NETDEV_CHANGENAME, and is for an interface
* we are taking care of, then we want to remove its existing
* proc entries (which now have the wrong names) and add
* new, correct, entries.
*/
static int
ieee80211_rcv_dev_event(struct notifier_block *this, unsigned long event,
void *ptr)
{
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,7,0)
struct net_device *dev = (struct net_device *) ptr;
#else
struct net_device *dev = netdev_notifier_info_to_dev(ptr);
#endif
if (!dev || dev->netdev_ops->ndo_open != &ieee80211_open)
return 0;
switch (event) {
case NETDEV_CHANGENAME:
ieee80211_sysctl_vdetach(netdev_priv(dev));
ieee80211_sysctl_vattach(netdev_priv(dev));
return NOTIFY_DONE;
default:
break;
}
return 0;
}
static struct notifier_block ieee80211_event_block = {
.notifier_call = ieee80211_rcv_dev_event
};
/*
* Module glue.
*/
#include "version.h"
#include "release.h"
static char *version = WLAN_VERSION " (" RELEASE_VERSION ")";
static char *dev_info = "wlan";
MODULE_AUTHOR("Errno Consulting, Sam Leffler");
MODULE_DESCRIPTION("802.11 wireless LAN protocol support");
#ifdef MODULE_VERSION
MODULE_VERSION(RELEASE_VERSION);
#endif
#ifdef MODULE_LICENSE
MODULE_LICENSE("Dual BSD/GPL");
#endif
extern void ieee80211_auth_setup(void);
static int __init
init_wlan(void)
{
register_netdevice_notifier(&ieee80211_event_block);
printk(KERN_INFO "%s: %s\n", dev_info, version);
return 0;
}
module_init(init_wlan);
static void __exit
exit_wlan(void)
{
unregister_netdevice_notifier(&ieee80211_event_block);
printk(KERN_INFO "%s: driver unloaded\n", dev_info);
}
module_exit(exit_wlan);