1045 lines
28 KiB
C
1045 lines
28 KiB
C
|
/*
|
||
|
* Copyright (c) 1999 - 2005 NetGroup, Politecnico di Torino (Italy)
|
||
|
* Copyright (c) 2005 - 2010 CACE Technologies, Davis (California)
|
||
|
* 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. Neither the name of the Politecnico di Torino, CACE Technologies
|
||
|
* nor the names of its contributors may be used to endorse or promote
|
||
|
* products derived from this software without specific prior written
|
||
|
* permission.
|
||
|
*
|
||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||
|
* "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 COPYRIGHT
|
||
|
* OWNER OR CONTRIBUTORS 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.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
#ifdef HAVE_CONFIG_H
|
||
|
#include <config.h>
|
||
|
#endif
|
||
|
|
||
|
#include "pcap-int.h"
|
||
|
|
||
|
#include <airpcap.h>
|
||
|
|
||
|
#include "pcap-airpcap.h"
|
||
|
|
||
|
/* Default size of the buffer we allocate in userland. */
|
||
|
#define AIRPCAP_DEFAULT_USER_BUFFER_SIZE 256000
|
||
|
|
||
|
/* Default size of the buffer for the AirPcap adapter. */
|
||
|
#define AIRPCAP_DEFAULT_KERNEL_BUFFER_SIZE 1000000
|
||
|
|
||
|
//
|
||
|
// We load the AirPcap DLL dynamically, so that the code will
|
||
|
// work whether you have it installed or not, and there don't
|
||
|
// have to be two different versions of the library, one linked
|
||
|
// to the AirPcap library and one not linked to it.
|
||
|
//
|
||
|
static pcap_code_handle_t airpcap_lib;
|
||
|
|
||
|
typedef PCHAR (*AirpcapGetLastErrorHandler)(PAirpcapHandle);
|
||
|
typedef BOOL (*AirpcapGetDeviceListHandler)(PAirpcapDeviceDescription *, PCHAR);
|
||
|
typedef VOID (*AirpcapFreeDeviceListHandler)(PAirpcapDeviceDescription);
|
||
|
typedef PAirpcapHandle (*AirpcapOpenHandler)(PCHAR, PCHAR);
|
||
|
typedef VOID (*AirpcapCloseHandler)(PAirpcapHandle);
|
||
|
typedef BOOL (*AirpcapSetDeviceMacFlagsHandler)(PAirpcapHandle, UINT);
|
||
|
typedef BOOL (*AirpcapSetLinkTypeHandler)(PAirpcapHandle, AirpcapLinkType);
|
||
|
typedef BOOL (*AirpcapGetLinkTypeHandler)(PAirpcapHandle, PAirpcapLinkType);
|
||
|
typedef BOOL (*AirpcapSetKernelBufferHandler)(PAirpcapHandle, UINT);
|
||
|
typedef BOOL (*AirpcapSetFilterHandler)(PAirpcapHandle, PVOID, UINT);
|
||
|
typedef BOOL (*AirpcapSetMinToCopyHandler)(PAirpcapHandle, UINT);
|
||
|
typedef BOOL (*AirpcapGetReadEventHandler)(PAirpcapHandle, HANDLE *);
|
||
|
typedef BOOL (*AirpcapReadHandler)(PAirpcapHandle, PBYTE, UINT, PUINT);
|
||
|
typedef BOOL (*AirpcapWriteHandler)(PAirpcapHandle, PCHAR, ULONG);
|
||
|
typedef BOOL (*AirpcapGetStatsHandler)(PAirpcapHandle, PAirpcapStats);
|
||
|
|
||
|
static AirpcapGetLastErrorHandler p_AirpcapGetLastError;
|
||
|
static AirpcapGetDeviceListHandler p_AirpcapGetDeviceList;
|
||
|
static AirpcapFreeDeviceListHandler p_AirpcapFreeDeviceList;
|
||
|
static AirpcapOpenHandler p_AirpcapOpen;
|
||
|
static AirpcapCloseHandler p_AirpcapClose;
|
||
|
static AirpcapSetDeviceMacFlagsHandler p_AirpcapSetDeviceMacFlags;
|
||
|
static AirpcapSetLinkTypeHandler p_AirpcapSetLinkType;
|
||
|
static AirpcapGetLinkTypeHandler p_AirpcapGetLinkType;
|
||
|
static AirpcapSetKernelBufferHandler p_AirpcapSetKernelBuffer;
|
||
|
static AirpcapSetFilterHandler p_AirpcapSetFilter;
|
||
|
static AirpcapSetMinToCopyHandler p_AirpcapSetMinToCopy;
|
||
|
static AirpcapGetReadEventHandler p_AirpcapGetReadEvent;
|
||
|
static AirpcapReadHandler p_AirpcapRead;
|
||
|
static AirpcapWriteHandler p_AirpcapWrite;
|
||
|
static AirpcapGetStatsHandler p_AirpcapGetStats;
|
||
|
|
||
|
typedef enum LONG
|
||
|
{
|
||
|
AIRPCAP_API_UNLOADED = 0,
|
||
|
AIRPCAP_API_LOADED,
|
||
|
AIRPCAP_API_CANNOT_LOAD,
|
||
|
AIRPCAP_API_LOADING
|
||
|
} AIRPCAP_API_LOAD_STATUS;
|
||
|
|
||
|
static AIRPCAP_API_LOAD_STATUS airpcap_load_status;
|
||
|
|
||
|
/*
|
||
|
* NOTE: this function should be called by the pcap functions that can
|
||
|
* theoretically deal with the AirPcap library for the first time,
|
||
|
* namely listing the adapters and creating a pcap_t for an adapter.
|
||
|
* All the other ones (activate, close, read, write, set parameters)
|
||
|
* work on a pcap_t for an AirPcap device, meaning we've already
|
||
|
* created the pcap_t and thus have loaded the functions, so we do
|
||
|
* not need to call this function.
|
||
|
*/
|
||
|
static AIRPCAP_API_LOAD_STATUS
|
||
|
load_airpcap_functions(void)
|
||
|
{
|
||
|
AIRPCAP_API_LOAD_STATUS current_status;
|
||
|
|
||
|
/*
|
||
|
* We don't use a mutex because there's no place that
|
||
|
* we can guarantee we'll be called before any threads
|
||
|
* other than the main thread exists. (For example,
|
||
|
* this might be a static library, so we can't arrange
|
||
|
* to be called by DllMain(), and there's no guarantee
|
||
|
* that the application called pcap_init() - which is
|
||
|
* supposed to be called only from one thread - so
|
||
|
* we can't arrange to be called from it.)
|
||
|
*
|
||
|
* If nobody's tried to load it yet, mark it as
|
||
|
* loading; in any case, return the status before
|
||
|
* we modified it.
|
||
|
*/
|
||
|
current_status = InterlockedCompareExchange((LONG *)&airpcap_load_status,
|
||
|
AIRPCAP_API_LOADING, AIRPCAP_API_UNLOADED);
|
||
|
|
||
|
/*
|
||
|
* If the status was AIRPCAP_API_UNLOADED, we've set it
|
||
|
* to AIRPCAP_API_LOADING, because we're going to be
|
||
|
* the ones to load the library but current_status is
|
||
|
* AIRPCAP_API_UNLOADED.
|
||
|
*
|
||
|
* if it was AIRPCAP_API_LOADING, meaning somebody else
|
||
|
* was trying to load it, spin until they finish and
|
||
|
* set the status to a value reflecting whether they
|
||
|
* succeeded.
|
||
|
*/
|
||
|
while (current_status == AIRPCAP_API_LOADING) {
|
||
|
current_status = InterlockedCompareExchange((LONG*)&airpcap_load_status,
|
||
|
AIRPCAP_API_LOADING, AIRPCAP_API_LOADING);
|
||
|
Sleep(10);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* At this point, current_status is either:
|
||
|
*
|
||
|
* AIRPCAP_API_LOADED, in which case another thread
|
||
|
* loaded the library, so we're done;
|
||
|
*
|
||
|
* AIRPCAP_API_CANNOT_LOAD, in which another thread
|
||
|
* tried and failed to load the library, so we're
|
||
|
* done - we won't try it ourselves;
|
||
|
*
|
||
|
* AIRPCAP_API_LOADING, in which case *we're* the
|
||
|
* ones loading it, and should now try to do so.
|
||
|
*/
|
||
|
if (current_status == AIRPCAP_API_LOADED)
|
||
|
return AIRPCAP_API_LOADED;
|
||
|
|
||
|
if (current_status == AIRPCAP_API_CANNOT_LOAD)
|
||
|
return AIRPCAP_API_CANNOT_LOAD;
|
||
|
|
||
|
/*
|
||
|
* Start out assuming we can't load it.
|
||
|
*/
|
||
|
current_status = AIRPCAP_API_CANNOT_LOAD;
|
||
|
|
||
|
airpcap_lib = pcap_load_code("airpcap.dll");
|
||
|
if (airpcap_lib != NULL) {
|
||
|
/*
|
||
|
* OK, we've loaded the library; now try to find the
|
||
|
* functions we need in it.
|
||
|
*/
|
||
|
p_AirpcapGetLastError = (AirpcapGetLastErrorHandler) pcap_find_function(airpcap_lib, "AirpcapGetLastError");
|
||
|
p_AirpcapGetDeviceList = (AirpcapGetDeviceListHandler) pcap_find_function(airpcap_lib, "AirpcapGetDeviceList");
|
||
|
p_AirpcapFreeDeviceList = (AirpcapFreeDeviceListHandler) pcap_find_function(airpcap_lib, "AirpcapFreeDeviceList");
|
||
|
p_AirpcapOpen = (AirpcapOpenHandler) pcap_find_function(airpcap_lib, "AirpcapOpen");
|
||
|
p_AirpcapClose = (AirpcapCloseHandler) pcap_find_function(airpcap_lib, "AirpcapClose");
|
||
|
p_AirpcapSetDeviceMacFlags = (AirpcapSetDeviceMacFlagsHandler) pcap_find_function(airpcap_lib, "AirpcapSetDeviceMacFlags");
|
||
|
p_AirpcapSetLinkType = (AirpcapSetLinkTypeHandler) pcap_find_function(airpcap_lib, "AirpcapSetLinkType");
|
||
|
p_AirpcapGetLinkType = (AirpcapGetLinkTypeHandler) pcap_find_function(airpcap_lib, "AirpcapGetLinkType");
|
||
|
p_AirpcapSetKernelBuffer = (AirpcapSetKernelBufferHandler) pcap_find_function(airpcap_lib, "AirpcapSetKernelBuffer");
|
||
|
p_AirpcapSetFilter = (AirpcapSetFilterHandler) pcap_find_function(airpcap_lib, "AirpcapSetFilter");
|
||
|
p_AirpcapSetMinToCopy = (AirpcapSetMinToCopyHandler) pcap_find_function(airpcap_lib, "AirpcapSetMinToCopy");
|
||
|
p_AirpcapGetReadEvent = (AirpcapGetReadEventHandler) pcap_find_function(airpcap_lib, "AirpcapGetReadEvent");
|
||
|
p_AirpcapRead = (AirpcapReadHandler) pcap_find_function(airpcap_lib, "AirpcapRead");
|
||
|
p_AirpcapWrite = (AirpcapWriteHandler) pcap_find_function(airpcap_lib, "AirpcapWrite");
|
||
|
p_AirpcapGetStats = (AirpcapGetStatsHandler) pcap_find_function(airpcap_lib, "AirpcapGetStats");
|
||
|
|
||
|
//
|
||
|
// Make sure that we found everything
|
||
|
//
|
||
|
if (p_AirpcapGetLastError != NULL &&
|
||
|
p_AirpcapGetDeviceList != NULL &&
|
||
|
p_AirpcapFreeDeviceList != NULL &&
|
||
|
p_AirpcapOpen != NULL &&
|
||
|
p_AirpcapClose != NULL &&
|
||
|
p_AirpcapSetDeviceMacFlags != NULL &&
|
||
|
p_AirpcapSetLinkType != NULL &&
|
||
|
p_AirpcapGetLinkType != NULL &&
|
||
|
p_AirpcapSetKernelBuffer != NULL &&
|
||
|
p_AirpcapSetFilter != NULL &&
|
||
|
p_AirpcapSetMinToCopy != NULL &&
|
||
|
p_AirpcapGetReadEvent != NULL &&
|
||
|
p_AirpcapRead != NULL &&
|
||
|
p_AirpcapWrite != NULL &&
|
||
|
p_AirpcapGetStats != NULL) {
|
||
|
/*
|
||
|
* We have all we need.
|
||
|
*/
|
||
|
current_status = AIRPCAP_API_LOADED;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (current_status != AIRPCAP_API_LOADED) {
|
||
|
/*
|
||
|
* We failed; if we found the DLL, close the
|
||
|
* handle for it.
|
||
|
*/
|
||
|
if (airpcap_lib != NULL) {
|
||
|
FreeLibrary(airpcap_lib);
|
||
|
airpcap_lib = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Now set the status appropriately - and atomically.
|
||
|
*/
|
||
|
InterlockedExchange((LONG *)&airpcap_load_status, current_status);
|
||
|
|
||
|
return current_status;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Private data for capturing on AirPcap devices.
|
||
|
*/
|
||
|
struct pcap_airpcap {
|
||
|
PAirpcapHandle adapter;
|
||
|
int filtering_in_kernel;
|
||
|
int nonblock;
|
||
|
int read_timeout;
|
||
|
HANDLE read_event;
|
||
|
struct pcap_stat stat;
|
||
|
};
|
||
|
|
||
|
static int
|
||
|
airpcap_setfilter(pcap_t *p, struct bpf_program *fp)
|
||
|
{
|
||
|
struct pcap_airpcap *pa = p->priv;
|
||
|
|
||
|
if (!p_AirpcapSetFilter(pa->adapter, fp->bf_insns,
|
||
|
fp->bf_len * sizeof(struct bpf_insn))) {
|
||
|
/*
|
||
|
* Kernel filter not installed.
|
||
|
*
|
||
|
* XXX - we don't know whether this failed because:
|
||
|
*
|
||
|
* the kernel rejected the filter program as invalid,
|
||
|
* in which case we should fall back on userland
|
||
|
* filtering;
|
||
|
*
|
||
|
* the kernel rejected the filter program as too big,
|
||
|
* in which case we should again fall back on
|
||
|
* userland filtering;
|
||
|
*
|
||
|
* there was some other problem, in which case we
|
||
|
* should probably report an error;
|
||
|
*
|
||
|
* So we just fall back on userland filtering in
|
||
|
* all cases.
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
* install_bpf_program() validates the program.
|
||
|
*
|
||
|
* XXX - what if we already have a filter in the kernel?
|
||
|
*/
|
||
|
if (install_bpf_program(p, fp) < 0)
|
||
|
return (-1);
|
||
|
pa->filtering_in_kernel = 0; /* filtering in userland */
|
||
|
return (0);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* It worked.
|
||
|
*/
|
||
|
pa->filtering_in_kernel = 1; /* filtering in the kernel */
|
||
|
|
||
|
/*
|
||
|
* Discard any previously-received packets, as they might have
|
||
|
* passed whatever filter was formerly in effect, but might
|
||
|
* not pass this filter (BIOCSETF discards packets buffered
|
||
|
* in the kernel, so you can lose packets in any case).
|
||
|
*/
|
||
|
p->cc = 0;
|
||
|
return (0);
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
airpcap_set_datalink(pcap_t *p, int dlt)
|
||
|
{
|
||
|
struct pcap_airpcap *pa = p->priv;
|
||
|
AirpcapLinkType type;
|
||
|
|
||
|
switch (dlt) {
|
||
|
|
||
|
case DLT_IEEE802_11_RADIO:
|
||
|
type = AIRPCAP_LT_802_11_PLUS_RADIO;
|
||
|
break;
|
||
|
|
||
|
case DLT_PPI:
|
||
|
type = AIRPCAP_LT_802_11_PLUS_PPI;
|
||
|
break;
|
||
|
|
||
|
case DLT_IEEE802_11:
|
||
|
type = AIRPCAP_LT_802_11;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
/* This can't happen; just return. */
|
||
|
return (0);
|
||
|
}
|
||
|
if (!p_AirpcapSetLinkType(pa->adapter, type)) {
|
||
|
snprintf(p->errbuf, PCAP_ERRBUF_SIZE,
|
||
|
"AirpcapSetLinkType() failed: %s",
|
||
|
p_AirpcapGetLastError(pa->adapter));
|
||
|
return (-1);
|
||
|
}
|
||
|
p->linktype = dlt;
|
||
|
return (0);
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
airpcap_getnonblock(pcap_t *p)
|
||
|
{
|
||
|
struct pcap_airpcap *pa = p->priv;
|
||
|
|
||
|
return (pa->nonblock);
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
airpcap_setnonblock(pcap_t *p, int nonblock)
|
||
|
{
|
||
|
struct pcap_airpcap *pa = p->priv;
|
||
|
int newtimeout;
|
||
|
|
||
|
if (nonblock) {
|
||
|
/*
|
||
|
* Set the packet buffer timeout to -1 for non-blocking
|
||
|
* mode.
|
||
|
*/
|
||
|
newtimeout = -1;
|
||
|
} else {
|
||
|
/*
|
||
|
* Restore the timeout set when the device was opened.
|
||
|
* (Note that this may be -1, in which case we're not
|
||
|
* really leaving non-blocking mode. However, although
|
||
|
* the timeout argument to pcap_set_timeout() and
|
||
|
* pcap_open_live() is an int, you're not supposed to
|
||
|
* supply a negative value, so that "shouldn't happen".)
|
||
|
*/
|
||
|
newtimeout = p->opt.timeout;
|
||
|
}
|
||
|
pa->read_timeout = newtimeout;
|
||
|
pa->nonblock = (newtimeout == -1);
|
||
|
return (0);
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
airpcap_stats(pcap_t *p, struct pcap_stat *ps)
|
||
|
{
|
||
|
struct pcap_airpcap *pa = p->priv;
|
||
|
AirpcapStats tas;
|
||
|
|
||
|
/*
|
||
|
* Try to get statistics.
|
||
|
*/
|
||
|
if (!p_AirpcapGetStats(pa->adapter, &tas)) {
|
||
|
snprintf(p->errbuf, PCAP_ERRBUF_SIZE,
|
||
|
"AirpcapGetStats() failed: %s",
|
||
|
p_AirpcapGetLastError(pa->adapter));
|
||
|
return (-1);
|
||
|
}
|
||
|
|
||
|
ps->ps_drop = tas.Drops;
|
||
|
ps->ps_recv = tas.Recvs;
|
||
|
ps->ps_ifdrop = tas.IfDrops;
|
||
|
|
||
|
return (0);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Win32-only routine for getting statistics.
|
||
|
*
|
||
|
* This way is definitely safer than passing the pcap_stat * from the userland.
|
||
|
* In fact, there could happen than the user allocates a variable which is not
|
||
|
* big enough for the new structure, and the library will write in a zone
|
||
|
* which is not allocated to this variable.
|
||
|
*
|
||
|
* In this way, we're pretty sure we are writing on memory allocated to this
|
||
|
* variable.
|
||
|
*
|
||
|
* XXX - but this is the wrong way to handle statistics. Instead, we should
|
||
|
* have an API that returns data in a form like the Options section of a
|
||
|
* pcapng Interface Statistics Block:
|
||
|
*
|
||
|
* https://xml2rfc.tools.ietf.org/cgi-bin/xml2rfc.cgi?url=https://raw.githubusercontent.com/pcapng/pcapng/master/draft-tuexen-opsawg-pcapng.xml&modeAsFormat=html/ascii&type=ascii#rfc.section.4.6
|
||
|
*
|
||
|
* which would let us add new statistics straightforwardly and indicate which
|
||
|
* statistics we are and are *not* providing, rather than having to provide
|
||
|
* possibly-bogus values for statistics we can't provide.
|
||
|
*/
|
||
|
static struct pcap_stat *
|
||
|
airpcap_stats_ex(pcap_t *p, int *pcap_stat_size)
|
||
|
{
|
||
|
struct pcap_airpcap *pa = p->priv;
|
||
|
AirpcapStats tas;
|
||
|
|
||
|
*pcap_stat_size = sizeof (p->stat);
|
||
|
|
||
|
/*
|
||
|
* Try to get statistics.
|
||
|
*/
|
||
|
if (!p_AirpcapGetStats(pa->adapter, &tas)) {
|
||
|
snprintf(p->errbuf, PCAP_ERRBUF_SIZE,
|
||
|
"AirpcapGetStats() failed: %s",
|
||
|
p_AirpcapGetLastError(pa->adapter));
|
||
|
return (NULL);
|
||
|
}
|
||
|
|
||
|
p->stat.ps_recv = tas.Recvs;
|
||
|
p->stat.ps_drop = tas.Drops;
|
||
|
p->stat.ps_ifdrop = tas.IfDrops;
|
||
|
/*
|
||
|
* Just in case this is ever compiled for a target other than
|
||
|
* Windows, which is extremely unlikely at best.
|
||
|
*/
|
||
|
#ifdef _WIN32
|
||
|
p->stat.ps_capt = tas.Capt;
|
||
|
#endif
|
||
|
return (&p->stat);
|
||
|
}
|
||
|
|
||
|
/* Set the dimension of the kernel-level capture buffer */
|
||
|
static int
|
||
|
airpcap_setbuff(pcap_t *p, int dim)
|
||
|
{
|
||
|
struct pcap_airpcap *pa = p->priv;
|
||
|
|
||
|
if (!p_AirpcapSetKernelBuffer(pa->adapter, dim)) {
|
||
|
snprintf(p->errbuf, PCAP_ERRBUF_SIZE,
|
||
|
"AirpcapSetKernelBuffer() failed: %s",
|
||
|
p_AirpcapGetLastError(pa->adapter));
|
||
|
return (-1);
|
||
|
}
|
||
|
return (0);
|
||
|
}
|
||
|
|
||
|
/* Set the driver working mode */
|
||
|
static int
|
||
|
airpcap_setmode(pcap_t *p, int mode)
|
||
|
{
|
||
|
if (mode != MODE_CAPT) {
|
||
|
snprintf(p->errbuf, PCAP_ERRBUF_SIZE,
|
||
|
"Only MODE_CAPT is supported on an AirPcap adapter");
|
||
|
return (-1);
|
||
|
}
|
||
|
return (0);
|
||
|
}
|
||
|
|
||
|
/*set the minimum amount of data that will release a read call*/
|
||
|
static int
|
||
|
airpcap_setmintocopy(pcap_t *p, int size)
|
||
|
{
|
||
|
struct pcap_airpcap *pa = p->priv;
|
||
|
|
||
|
if (!p_AirpcapSetMinToCopy(pa->adapter, size)) {
|
||
|
snprintf(p->errbuf, PCAP_ERRBUF_SIZE,
|
||
|
"AirpcapSetMinToCopy() failed: %s",
|
||
|
p_AirpcapGetLastError(pa->adapter));
|
||
|
return (-1);
|
||
|
}
|
||
|
return (0);
|
||
|
}
|
||
|
|
||
|
static HANDLE
|
||
|
airpcap_getevent(pcap_t *p)
|
||
|
{
|
||
|
struct pcap_airpcap *pa = p->priv;
|
||
|
|
||
|
return (pa->read_event);
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
airpcap_oid_get_request(pcap_t *p, bpf_u_int32 oid _U_, void *data _U_,
|
||
|
size_t *lenp _U_)
|
||
|
{
|
||
|
snprintf(p->errbuf, PCAP_ERRBUF_SIZE,
|
||
|
"Getting OID values is not supported on an AirPcap adapter");
|
||
|
return (PCAP_ERROR);
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
airpcap_oid_set_request(pcap_t *p, bpf_u_int32 oid _U_, const void *data _U_,
|
||
|
size_t *lenp _U_)
|
||
|
{
|
||
|
snprintf(p->errbuf, PCAP_ERRBUF_SIZE,
|
||
|
"Setting OID values is not supported on an AirPcap adapter");
|
||
|
return (PCAP_ERROR);
|
||
|
}
|
||
|
|
||
|
static u_int
|
||
|
airpcap_sendqueue_transmit(pcap_t *p, pcap_send_queue *queue _U_, int sync _U_)
|
||
|
{
|
||
|
snprintf(p->errbuf, PCAP_ERRBUF_SIZE,
|
||
|
"Cannot queue packets for transmission on an AirPcap adapter");
|
||
|
return (0);
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
airpcap_setuserbuffer(pcap_t *p, int size)
|
||
|
{
|
||
|
unsigned char *new_buff;
|
||
|
|
||
|
if (size <= 0) {
|
||
|
/* Bogus parameter */
|
||
|
snprintf(p->errbuf, PCAP_ERRBUF_SIZE,
|
||
|
"Error: invalid size %d",size);
|
||
|
return (-1);
|
||
|
}
|
||
|
|
||
|
/* Allocate the buffer */
|
||
|
new_buff = (unsigned char *)malloc(sizeof(char)*size);
|
||
|
|
||
|
if (!new_buff) {
|
||
|
snprintf(p->errbuf, PCAP_ERRBUF_SIZE,
|
||
|
"Error: not enough memory");
|
||
|
return (-1);
|
||
|
}
|
||
|
|
||
|
free(p->buffer);
|
||
|
|
||
|
p->buffer = new_buff;
|
||
|
p->bufsize = size;
|
||
|
|
||
|
return (0);
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
airpcap_live_dump(pcap_t *p, char *filename _U_, int maxsize _U_,
|
||
|
int maxpacks _U_)
|
||
|
{
|
||
|
snprintf(p->errbuf, PCAP_ERRBUF_SIZE,
|
||
|
"AirPcap adapters don't support live dump");
|
||
|
return (-1);
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
airpcap_live_dump_ended(pcap_t *p, int sync _U_)
|
||
|
{
|
||
|
snprintf(p->errbuf, PCAP_ERRBUF_SIZE,
|
||
|
"AirPcap adapters don't support live dump");
|
||
|
return (-1);
|
||
|
}
|
||
|
|
||
|
static PAirpcapHandle
|
||
|
airpcap_get_airpcap_handle(pcap_t *p)
|
||
|
{
|
||
|
struct pcap_airpcap *pa = p->priv;
|
||
|
|
||
|
return (pa->adapter);
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
airpcap_read(pcap_t *p, int cnt, pcap_handler callback, u_char *user)
|
||
|
{
|
||
|
struct pcap_airpcap *pa = p->priv;
|
||
|
int cc;
|
||
|
int n;
|
||
|
register u_char *bp, *ep;
|
||
|
UINT bytes_read;
|
||
|
u_char *datap;
|
||
|
|
||
|
cc = p->cc;
|
||
|
if (cc == 0) {
|
||
|
/*
|
||
|
* Has "pcap_breakloop()" been called?
|
||
|
*/
|
||
|
if (p->break_loop) {
|
||
|
/*
|
||
|
* Yes - clear the flag that indicates that it
|
||
|
* has, and return PCAP_ERROR_BREAK to indicate
|
||
|
* that we were told to break out of the loop.
|
||
|
*/
|
||
|
p->break_loop = 0;
|
||
|
return (PCAP_ERROR_BREAK);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// If we're not in non-blocking mode, wait for data to
|
||
|
// arrive.
|
||
|
//
|
||
|
if (pa->read_timeout != -1) {
|
||
|
WaitForSingleObject(pa->read_event,
|
||
|
(pa->read_timeout ==0 )? INFINITE: pa->read_timeout);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Read the data.
|
||
|
// p_AirpcapRead doesn't block.
|
||
|
//
|
||
|
if (!p_AirpcapRead(pa->adapter, (PBYTE)p->buffer,
|
||
|
p->bufsize, &bytes_read)) {
|
||
|
snprintf(p->errbuf, PCAP_ERRBUF_SIZE,
|
||
|
"AirpcapRead() failed: %s",
|
||
|
p_AirpcapGetLastError(pa->adapter));
|
||
|
return (-1);
|
||
|
}
|
||
|
cc = bytes_read;
|
||
|
bp = (u_char *)p->buffer;
|
||
|
} else
|
||
|
bp = p->bp;
|
||
|
|
||
|
/*
|
||
|
* Loop through each packet.
|
||
|
*/
|
||
|
#define bhp ((AirpcapBpfHeader *)bp)
|
||
|
n = 0;
|
||
|
ep = bp + cc;
|
||
|
for (;;) {
|
||
|
register u_int caplen, hdrlen;
|
||
|
|
||
|
/*
|
||
|
* Has "pcap_breakloop()" been called?
|
||
|
* If so, return immediately - if we haven't read any
|
||
|
* packets, clear the flag and return PCAP_ERROR_BREAK
|
||
|
* to indicate that we were told to break out of the loop,
|
||
|
* otherwise leave the flag set, so that the *next* call
|
||
|
* will break out of the loop without having read any
|
||
|
* packets, and return the number of packets we've
|
||
|
* processed so far.
|
||
|
*/
|
||
|
if (p->break_loop) {
|
||
|
if (n == 0) {
|
||
|
p->break_loop = 0;
|
||
|
return (PCAP_ERROR_BREAK);
|
||
|
} else {
|
||
|
p->bp = bp;
|
||
|
p->cc = (int) (ep - bp);
|
||
|
return (n);
|
||
|
}
|
||
|
}
|
||
|
if (bp >= ep)
|
||
|
break;
|
||
|
|
||
|
caplen = bhp->Caplen;
|
||
|
hdrlen = bhp->Hdrlen;
|
||
|
datap = bp + hdrlen;
|
||
|
/*
|
||
|
* Short-circuit evaluation: if using BPF filter
|
||
|
* in the AirPcap adapter, no need to do it now -
|
||
|
* we already know the packet passed the filter.
|
||
|
*/
|
||
|
if (pa->filtering_in_kernel ||
|
||
|
p->fcode.bf_insns == NULL ||
|
||
|
pcap_filter(p->fcode.bf_insns, datap, bhp->Originallen, caplen)) {
|
||
|
struct pcap_pkthdr pkthdr;
|
||
|
|
||
|
pkthdr.ts.tv_sec = bhp->TsSec;
|
||
|
pkthdr.ts.tv_usec = bhp->TsUsec;
|
||
|
pkthdr.caplen = caplen;
|
||
|
pkthdr.len = bhp->Originallen;
|
||
|
(*callback)(user, &pkthdr, datap);
|
||
|
bp += AIRPCAP_WORDALIGN(caplen + hdrlen);
|
||
|
if (++n >= cnt && !PACKET_COUNT_IS_UNLIMITED(cnt)) {
|
||
|
p->bp = bp;
|
||
|
p->cc = (int)(ep - bp);
|
||
|
return (n);
|
||
|
}
|
||
|
} else {
|
||
|
/*
|
||
|
* Skip this packet.
|
||
|
*/
|
||
|
bp += AIRPCAP_WORDALIGN(caplen + hdrlen);
|
||
|
}
|
||
|
}
|
||
|
#undef bhp
|
||
|
p->cc = 0;
|
||
|
return (n);
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
airpcap_inject(pcap_t *p, const void *buf, int size)
|
||
|
{
|
||
|
struct pcap_airpcap *pa = p->priv;
|
||
|
|
||
|
/*
|
||
|
* XXX - the second argument to AirpcapWrite() *should* have
|
||
|
* been declared as a const pointer - a write function that
|
||
|
* stomps on what it writes is *extremely* rude - but such
|
||
|
* is life. We assume it is, in fact, not going to write on
|
||
|
* our buffer.
|
||
|
*/
|
||
|
if (!p_AirpcapWrite(pa->adapter, (void *)buf, size)) {
|
||
|
snprintf(p->errbuf, PCAP_ERRBUF_SIZE,
|
||
|
"AirpcapWrite() failed: %s",
|
||
|
p_AirpcapGetLastError(pa->adapter));
|
||
|
return (-1);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* We assume it all got sent if "AirpcapWrite()" succeeded.
|
||
|
* "pcap_inject()" is expected to return the number of bytes
|
||
|
* sent.
|
||
|
*/
|
||
|
return (size);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
airpcap_cleanup(pcap_t *p)
|
||
|
{
|
||
|
struct pcap_airpcap *pa = p->priv;
|
||
|
|
||
|
if (pa->adapter != NULL) {
|
||
|
p_AirpcapClose(pa->adapter);
|
||
|
pa->adapter = NULL;
|
||
|
}
|
||
|
pcap_cleanup_live_common(p);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
airpcap_breakloop(pcap_t *p)
|
||
|
{
|
||
|
HANDLE read_event;
|
||
|
|
||
|
pcap_breakloop_common(p);
|
||
|
struct pcap_airpcap *pa = p->priv;
|
||
|
|
||
|
/* XXX - what if either of these fail? */
|
||
|
/*
|
||
|
* XXX - will SetEvent() force a wakeup and, if so, will
|
||
|
* the AirPcap read code handle that sanely?
|
||
|
*/
|
||
|
if (!p_AirpcapGetReadEvent(pa->adapter, &read_event))
|
||
|
return;
|
||
|
SetEvent(read_event);
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
airpcap_activate(pcap_t *p)
|
||
|
{
|
||
|
struct pcap_airpcap *pa = p->priv;
|
||
|
char *device = p->opt.device;
|
||
|
char airpcap_errbuf[AIRPCAP_ERRBUF_SIZE];
|
||
|
BOOL status;
|
||
|
AirpcapLinkType link_type;
|
||
|
|
||
|
pa->adapter = p_AirpcapOpen(device, airpcap_errbuf);
|
||
|
if (pa->adapter == NULL) {
|
||
|
snprintf(p->errbuf, PCAP_ERRBUF_SIZE, "%s", airpcap_errbuf);
|
||
|
return (PCAP_ERROR);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Set monitor mode appropriately.
|
||
|
* Always turn off the "ACK frames sent to the card" mode.
|
||
|
*/
|
||
|
if (p->opt.rfmon) {
|
||
|
status = p_AirpcapSetDeviceMacFlags(pa->adapter,
|
||
|
AIRPCAP_MF_MONITOR_MODE_ON);
|
||
|
} else
|
||
|
status = p_AirpcapSetDeviceMacFlags(pa->adapter,
|
||
|
AIRPCAP_MF_ACK_FRAMES_ON);
|
||
|
if (!status) {
|
||
|
p_AirpcapClose(pa->adapter);
|
||
|
snprintf(p->errbuf, PCAP_ERRBUF_SIZE,
|
||
|
"AirpcapSetDeviceMacFlags() failed: %s",
|
||
|
p_AirpcapGetLastError(pa->adapter));
|
||
|
return (PCAP_ERROR);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Turn a negative snapshot value (invalid), a snapshot value of
|
||
|
* 0 (unspecified), or a value bigger than the normal maximum
|
||
|
* value, into the maximum allowed value.
|
||
|
*
|
||
|
* If some application really *needs* a bigger snapshot
|
||
|
* length, we should just increase MAXIMUM_SNAPLEN.
|
||
|
*/
|
||
|
if (p->snapshot <= 0 || p->snapshot > MAXIMUM_SNAPLEN)
|
||
|
p->snapshot = MAXIMUM_SNAPLEN;
|
||
|
|
||
|
/*
|
||
|
* If the buffer size wasn't explicitly set, default to
|
||
|
* AIRPCAP_DEFAULT_KERNEL_BUFFER_SIZE.
|
||
|
*/
|
||
|
if (p->opt.buffer_size == 0)
|
||
|
p->opt.buffer_size = AIRPCAP_DEFAULT_KERNEL_BUFFER_SIZE;
|
||
|
|
||
|
if (!p_AirpcapSetKernelBuffer(pa->adapter, p->opt.buffer_size)) {
|
||
|
snprintf(p->errbuf, PCAP_ERRBUF_SIZE,
|
||
|
"AirpcapSetKernelBuffer() failed: %s",
|
||
|
p_AirpcapGetLastError(pa->adapter));
|
||
|
goto bad;
|
||
|
}
|
||
|
|
||
|
if(!p_AirpcapGetReadEvent(pa->adapter, &pa->read_event)) {
|
||
|
snprintf(p->errbuf, PCAP_ERRBUF_SIZE,
|
||
|
"AirpcapGetReadEvent() failed: %s",
|
||
|
p_AirpcapGetLastError(pa->adapter));
|
||
|
goto bad;
|
||
|
}
|
||
|
|
||
|
/* Set the buffer size */
|
||
|
p->bufsize = AIRPCAP_DEFAULT_USER_BUFFER_SIZE;
|
||
|
p->buffer = malloc(p->bufsize);
|
||
|
if (p->buffer == NULL) {
|
||
|
pcap_fmt_errmsg_for_errno(p->errbuf, PCAP_ERRBUF_SIZE,
|
||
|
errno, "malloc");
|
||
|
goto bad;
|
||
|
}
|
||
|
|
||
|
if (p->opt.immediate) {
|
||
|
/* Tell the driver to copy the buffer as soon as data arrives. */
|
||
|
if (!p_AirpcapSetMinToCopy(pa->adapter, 0)) {
|
||
|
snprintf(p->errbuf, PCAP_ERRBUF_SIZE,
|
||
|
"AirpcapSetMinToCopy() failed: %s",
|
||
|
p_AirpcapGetLastError(pa->adapter));
|
||
|
goto bad;
|
||
|
}
|
||
|
} else {
|
||
|
/*
|
||
|
* Tell the driver to copy the buffer only if it contains
|
||
|
* at least 16K.
|
||
|
*/
|
||
|
if (!p_AirpcapSetMinToCopy(pa->adapter, 16000)) {
|
||
|
snprintf(p->errbuf, PCAP_ERRBUF_SIZE,
|
||
|
"AirpcapSetMinToCopy() failed: %s",
|
||
|
p_AirpcapGetLastError(pa->adapter));
|
||
|
goto bad;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Find out what the default link-layer header type is,
|
||
|
* and set p->datalink to that.
|
||
|
*
|
||
|
* We don't force it to another value because there
|
||
|
* might be some programs using WinPcap/Npcap that,
|
||
|
* when capturing on AirPcap devices, assume the
|
||
|
* default value set with the AirPcap configuration
|
||
|
* program is what you get.
|
||
|
*
|
||
|
* The out-of-the-box default appears to be radiotap.
|
||
|
*/
|
||
|
if (!p_AirpcapGetLinkType(pa->adapter, &link_type)) {
|
||
|
/* That failed. */
|
||
|
snprintf(p->errbuf, PCAP_ERRBUF_SIZE,
|
||
|
"AirpcapGetLinkType() failed: %s",
|
||
|
p_AirpcapGetLastError(pa->adapter));
|
||
|
goto bad;
|
||
|
}
|
||
|
switch (link_type) {
|
||
|
|
||
|
case AIRPCAP_LT_802_11_PLUS_RADIO:
|
||
|
p->linktype = DLT_IEEE802_11_RADIO;
|
||
|
break;
|
||
|
|
||
|
case AIRPCAP_LT_802_11_PLUS_PPI:
|
||
|
p->linktype = DLT_PPI;
|
||
|
break;
|
||
|
|
||
|
case AIRPCAP_LT_802_11:
|
||
|
p->linktype = DLT_IEEE802_11;
|
||
|
break;
|
||
|
|
||
|
case AIRPCAP_LT_UNKNOWN:
|
||
|
default:
|
||
|
/* OK, what? */
|
||
|
snprintf(p->errbuf, PCAP_ERRBUF_SIZE,
|
||
|
"AirpcapGetLinkType() returned unknown link type %u",
|
||
|
link_type);
|
||
|
goto bad;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Now provide a list of all the supported types; we
|
||
|
* assume they all work. We put radiotap at the top,
|
||
|
* followed by PPI, followed by "no radio metadata".
|
||
|
*/
|
||
|
p->dlt_list = (u_int *) malloc(sizeof(u_int) * 3);
|
||
|
if (p->dlt_list == NULL)
|
||
|
goto bad;
|
||
|
p->dlt_list[0] = DLT_IEEE802_11_RADIO;
|
||
|
p->dlt_list[1] = DLT_PPI;
|
||
|
p->dlt_list[2] = DLT_IEEE802_11;
|
||
|
p->dlt_count = 3;
|
||
|
|
||
|
p->read_op = airpcap_read;
|
||
|
p->inject_op = airpcap_inject;
|
||
|
p->setfilter_op = airpcap_setfilter;
|
||
|
p->setdirection_op = NULL; /* Not implemented. */
|
||
|
p->set_datalink_op = airpcap_set_datalink;
|
||
|
p->getnonblock_op = airpcap_getnonblock;
|
||
|
p->setnonblock_op = airpcap_setnonblock;
|
||
|
p->breakloop_op = airpcap_breakloop;
|
||
|
p->stats_op = airpcap_stats;
|
||
|
p->stats_ex_op = airpcap_stats_ex;
|
||
|
p->setbuff_op = airpcap_setbuff;
|
||
|
p->setmode_op = airpcap_setmode;
|
||
|
p->setmintocopy_op = airpcap_setmintocopy;
|
||
|
p->getevent_op = airpcap_getevent;
|
||
|
p->oid_get_request_op = airpcap_oid_get_request;
|
||
|
p->oid_set_request_op = airpcap_oid_set_request;
|
||
|
p->sendqueue_transmit_op = airpcap_sendqueue_transmit;
|
||
|
p->setuserbuffer_op = airpcap_setuserbuffer;
|
||
|
p->live_dump_op = airpcap_live_dump;
|
||
|
p->live_dump_ended_op = airpcap_live_dump_ended;
|
||
|
p->get_airpcap_handle_op = airpcap_get_airpcap_handle;
|
||
|
p->cleanup_op = airpcap_cleanup;
|
||
|
|
||
|
return (0);
|
||
|
bad:
|
||
|
airpcap_cleanup(p);
|
||
|
return (PCAP_ERROR);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Monitor mode is supported.
|
||
|
*/
|
||
|
static int
|
||
|
airpcap_can_set_rfmon(pcap_t *p)
|
||
|
{
|
||
|
return (1);
|
||
|
}
|
||
|
|
||
|
int
|
||
|
device_is_airpcap(const char *device, char *ebuf)
|
||
|
{
|
||
|
static const char airpcap_prefix[] = "\\\\.\\airpcap";
|
||
|
|
||
|
/*
|
||
|
* We don't determine this by calling AirpcapGetDeviceList()
|
||
|
* and looking at the list, as that appears to be a costly
|
||
|
* operation.
|
||
|
*
|
||
|
* Instead, we just check whether it begins with "\\.\airpcap".
|
||
|
*/
|
||
|
if (strncmp(device, airpcap_prefix, sizeof airpcap_prefix - 1) == 0) {
|
||
|
/*
|
||
|
* Yes, it's an AirPcap device.
|
||
|
*/
|
||
|
return (1);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* No, it's not an AirPcap device.
|
||
|
*/
|
||
|
return (0);
|
||
|
}
|
||
|
|
||
|
pcap_t *
|
||
|
airpcap_create(const char *device, char *ebuf, int *is_ours)
|
||
|
{
|
||
|
int ret;
|
||
|
pcap_t *p;
|
||
|
|
||
|
/*
|
||
|
* This can be called before we've tried loading the library,
|
||
|
* so do so if we haven't already tried to do so.
|
||
|
*/
|
||
|
if (load_airpcap_functions() != AIRPCAP_API_LOADED) {
|
||
|
snprintf(ebuf, PCAP_ERRBUF_SIZE, "Couldn't load AirPcap DLL\n");
|
||
|
return (NULL);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Is this an AirPcap device?
|
||
|
*/
|
||
|
ret = device_is_airpcap(device, ebuf);
|
||
|
if (ret == 0) {
|
||
|
/* No. */
|
||
|
*is_ours = 0;
|
||
|
return (NULL);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Yes.
|
||
|
*/
|
||
|
*is_ours = 1;
|
||
|
p = PCAP_CREATE_COMMON(ebuf, struct pcap_airpcap);
|
||
|
if (p == NULL)
|
||
|
return (NULL);
|
||
|
|
||
|
p->activate_op = airpcap_activate;
|
||
|
p->can_set_rfmon_op = airpcap_can_set_rfmon;
|
||
|
return (p);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Add all AirPcap devices.
|
||
|
*/
|
||
|
int
|
||
|
airpcap_findalldevs(pcap_if_list_t *devlistp, char *errbuf)
|
||
|
{
|
||
|
AirpcapDeviceDescription *airpcap_devices, *airpcap_device;
|
||
|
char airpcap_errbuf[AIRPCAP_ERRBUF_SIZE];
|
||
|
|
||
|
/*
|
||
|
* This can be called before we've tried loading the library,
|
||
|
* so do so if we haven't already tried to do so.
|
||
|
*/
|
||
|
if (load_airpcap_functions() != AIRPCAP_API_LOADED) {
|
||
|
/*
|
||
|
* XXX - unless the error is "no such DLL", report this
|
||
|
* as an error rather than as "no AirPcap devices"?
|
||
|
*/
|
||
|
return (0);
|
||
|
}
|
||
|
|
||
|
if (!p_AirpcapGetDeviceList(&airpcap_devices, airpcap_errbuf)) {
|
||
|
snprintf(errbuf, PCAP_ERRBUF_SIZE,
|
||
|
"AirpcapGetDeviceList() failed: %s", airpcap_errbuf);
|
||
|
return (-1);
|
||
|
}
|
||
|
|
||
|
for (airpcap_device = airpcap_devices; airpcap_device != NULL;
|
||
|
airpcap_device = airpcap_device->next) {
|
||
|
if (add_dev(devlistp, airpcap_device->Name, 0,
|
||
|
airpcap_device->Description, errbuf) == NULL) {
|
||
|
/*
|
||
|
* Failure.
|
||
|
*/
|
||
|
p_AirpcapFreeDeviceList(airpcap_devices);
|
||
|
return (-1);
|
||
|
}
|
||
|
}
|
||
|
p_AirpcapFreeDeviceList(airpcap_devices);
|
||
|
return (0);
|
||
|
}
|