| /* |
| * Copyright (c) 2008 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 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.h> |
| #include <pcap-int.h> |
| |
| #include "pcap-tc.h" |
| |
| #include <malloc.h> |
| #include <memory.h> |
| #include <string.h> |
| #include <errno.h> |
| |
| #ifdef _WIN32 |
| #include <tchar.h> |
| #endif |
| |
| typedef TC_STATUS (TC_CALLCONV *TcFcnQueryPortList) (PTC_PORT *ppPorts, PULONG pLength); |
| typedef TC_STATUS (TC_CALLCONV *TcFcnFreePortList) (TC_PORT *pPorts); |
| |
| typedef PCHAR (TC_CALLCONV *TcFcnStatusGetString) (TC_STATUS status); |
| |
| typedef PCHAR (TC_CALLCONV *TcFcnPortGetName) (TC_PORT port); |
| typedef PCHAR (TC_CALLCONV *TcFcnPortGetDescription) (TC_PORT port); |
| |
| typedef TC_STATUS (TC_CALLCONV *TcFcnInstanceOpenByName) (PCHAR name, PTC_INSTANCE pInstance); |
| typedef TC_STATUS (TC_CALLCONV *TcFcnInstanceClose) (TC_INSTANCE instance); |
| typedef TC_STATUS (TC_CALLCONV *TcFcnInstanceSetFeature) (TC_INSTANCE instance, ULONG feature, ULONG value); |
| typedef TC_STATUS (TC_CALLCONV *TcFcnInstanceQueryFeature) (TC_INSTANCE instance, ULONG feature, PULONG pValue); |
| typedef TC_STATUS (TC_CALLCONV *TcFcnInstanceReceivePackets) (TC_INSTANCE instance, PTC_PACKETS_BUFFER pBuffer); |
| typedef HANDLE (TC_CALLCONV *TcFcnInstanceGetReceiveWaitHandle) (TC_INSTANCE instance); |
| typedef TC_STATUS (TC_CALLCONV *TcFcnInstanceTransmitPackets) (TC_INSTANCE instance, TC_PACKETS_BUFFER pBuffer); |
| typedef TC_STATUS (TC_CALLCONV *TcFcnInstanceQueryStatistics) (TC_INSTANCE instance, PTC_STATISTICS pStatistics); |
| |
| typedef TC_STATUS (TC_CALLCONV *TcFcnPacketsBufferCreate) (ULONG size, PTC_PACKETS_BUFFER pBuffer); |
| typedef VOID (TC_CALLCONV *TcFcnPacketsBufferDestroy) (TC_PACKETS_BUFFER buffer); |
| typedef TC_STATUS (TC_CALLCONV *TcFcnPacketsBufferQueryNextPacket)(TC_PACKETS_BUFFER buffer, PTC_PACKET_HEADER pHeader, PVOID *ppData); |
| typedef TC_STATUS (TC_CALLCONV *TcFcnPacketsBufferCommitNextPacket)(TC_PACKETS_BUFFER buffer, PTC_PACKET_HEADER pHeader, PVOID pData); |
| |
| typedef VOID (TC_CALLCONV *TcFcnStatisticsDestroy) (TC_STATISTICS statistics); |
| typedef TC_STATUS (TC_CALLCONV *TcFcnStatisticsUpdate) (TC_STATISTICS statistics); |
| typedef TC_STATUS (TC_CALLCONV *TcFcnStatisticsQueryValue) (TC_STATISTICS statistics, ULONG counterId, PULONGLONG pValue); |
| |
| typedef enum LONG |
| { |
| TC_API_UNLOADED = 0, |
| TC_API_LOADED, |
| TC_API_CANNOT_LOAD, |
| TC_API_LOADING |
| } |
| TC_API_LOAD_STATUS; |
| |
| |
| typedef struct _TC_FUNCTIONS |
| { |
| TC_API_LOAD_STATUS LoadStatus; |
| #ifdef _WIN32 |
| HMODULE hTcApiDllHandle; |
| #endif |
| TcFcnQueryPortList QueryPortList; |
| TcFcnFreePortList FreePortList; |
| TcFcnStatusGetString StatusGetString; |
| |
| TcFcnPortGetName PortGetName; |
| TcFcnPortGetDescription PortGetDescription; |
| |
| TcFcnInstanceOpenByName InstanceOpenByName; |
| TcFcnInstanceClose InstanceClose; |
| TcFcnInstanceSetFeature InstanceSetFeature; |
| TcFcnInstanceQueryFeature InstanceQueryFeature; |
| TcFcnInstanceReceivePackets InstanceReceivePackets; |
| #ifdef _WIN32 |
| TcFcnInstanceGetReceiveWaitHandle InstanceGetReceiveWaitHandle; |
| #endif |
| TcFcnInstanceTransmitPackets InstanceTransmitPackets; |
| TcFcnInstanceQueryStatistics InstanceQueryStatistics; |
| |
| TcFcnPacketsBufferCreate PacketsBufferCreate; |
| TcFcnPacketsBufferDestroy PacketsBufferDestroy; |
| TcFcnPacketsBufferQueryNextPacket PacketsBufferQueryNextPacket; |
| TcFcnPacketsBufferCommitNextPacket PacketsBufferCommitNextPacket; |
| |
| TcFcnStatisticsDestroy StatisticsDestroy; |
| TcFcnStatisticsUpdate StatisticsUpdate; |
| TcFcnStatisticsQueryValue StatisticsQueryValue; |
| } |
| TC_FUNCTIONS; |
| |
| static pcap_if_t* TcCreatePcapIfFromPort(TC_PORT port); |
| static int TcSetDatalink(pcap_t *p, int dlt); |
| static int TcGetNonBlock(pcap_t *p); |
| static int TcSetNonBlock(pcap_t *p, int nonblock); |
| static void TcCleanup(pcap_t *p); |
| static int TcInject(pcap_t *p, const void *buf, int size); |
| static int TcRead(pcap_t *p, int cnt, pcap_handler callback, u_char *user); |
| static int TcStats(pcap_t *p, struct pcap_stat *ps); |
| #ifdef _WIN32 |
| static struct pcap_stat *TcStatsEx(pcap_t *p, int *pcap_stat_size); |
| static int TcSetBuff(pcap_t *p, int dim); |
| static int TcSetMode(pcap_t *p, int mode); |
| static int TcSetMinToCopy(pcap_t *p, int size); |
| static HANDLE TcGetReceiveWaitHandle(pcap_t *p); |
| static int TcOidGetRequest(pcap_t *p, bpf_u_int32 oid, void *data, size_t *lenp); |
| static int TcOidSetRequest(pcap_t *p, bpf_u_int32 oid, const void *data, size_t *lenp); |
| static u_int TcSendqueueTransmit(pcap_t *p, pcap_send_queue *queue, int sync); |
| static int TcSetUserBuffer(pcap_t *p, int size); |
| static int TcLiveDump(pcap_t *p, char *filename, int maxsize, int maxpacks); |
| static int TcLiveDumpEnded(pcap_t *p, int sync); |
| static PAirpcapHandle TcGetAirPcapHandle(pcap_t *p); |
| #endif |
| |
| #ifdef _WIN32 |
| TC_FUNCTIONS g_TcFunctions = |
| { |
| TC_API_UNLOADED, /* LoadStatus */ |
| NULL, /* hTcApiDllHandle */ |
| NULL, /* QueryPortList */ |
| NULL, /* FreePortList */ |
| NULL, /* StatusGetString */ |
| NULL, /* PortGetName */ |
| NULL, /* PortGetDescription */ |
| NULL, /* InstanceOpenByName */ |
| NULL, /* InstanceClose */ |
| NULL, /* InstanceSetFeature */ |
| NULL, /* InstanceQueryFeature */ |
| NULL, /* InstanceReceivePackets */ |
| NULL, /* InstanceGetReceiveWaitHandle */ |
| NULL, /* InstanceTransmitPackets */ |
| NULL, /* InstanceQueryStatistics */ |
| NULL, /* PacketsBufferCreate */ |
| NULL, /* PacketsBufferDestroy */ |
| NULL, /* PacketsBufferQueryNextPacket */ |
| NULL, /* PacketsBufferCommitNextPacket */ |
| NULL, /* StatisticsDestroy */ |
| NULL, /* StatisticsUpdate */ |
| NULL /* StatisticsQueryValue */ |
| }; |
| #else |
| TC_FUNCTIONS g_TcFunctions = |
| { |
| TC_API_LOADED, /* LoadStatus */ |
| TcQueryPortList, |
| TcFreePortList, |
| TcStatusGetString, |
| TcPortGetName, |
| TcPortGetDescription, |
| TcInstanceOpenByName, |
| TcInstanceClose, |
| TcInstanceSetFeature, |
| TcInstanceQueryFeature, |
| TcInstanceReceivePackets, |
| #ifdef _WIN32 |
| TcInstanceGetReceiveWaitHandle, |
| #endif |
| TcInstanceTransmitPackets, |
| TcInstanceQueryStatistics, |
| TcPacketsBufferCreate, |
| TcPacketsBufferDestroy, |
| TcPacketsBufferQueryNextPacket, |
| TcPacketsBufferCommitNextPacket, |
| TcStatisticsDestroy, |
| TcStatisticsUpdate, |
| TcStatisticsQueryValue, |
| }; |
| #endif |
| |
| #define MAX_TC_PACKET_SIZE 9500 |
| |
| #pragma pack(push, 1) |
| |
| #define PPH_PH_FLAG_PADDING ((UCHAR)0x01) |
| #define PPH_PH_VERSION ((UCHAR)0x00) |
| |
| typedef struct _PPI_PACKET_HEADER |
| { |
| UCHAR PphVersion; |
| UCHAR PphFlags; |
| USHORT PphLength; |
| ULONG PphDlt; |
| } |
| PPI_PACKET_HEADER, *PPPI_PACKET_HEADER; |
| |
| typedef struct _PPI_FIELD_HEADER |
| { |
| USHORT PfhType; |
| USHORT PfhLength; |
| } |
| PPI_FIELD_HEADER, *PPPI_FIELD_HEADER; |
| |
| |
| #define PPI_FIELD_TYPE_AGGREGATION_EXTENSION ((UCHAR)0x08) |
| |
| typedef struct _PPI_FIELD_AGGREGATION_EXTENSION |
| { |
| ULONG InterfaceId; |
| } |
| PPI_FIELD_AGGREGATION_EXTENSION, *PPPI_FIELD_AGGREGATION_EXTENSION; |
| |
| |
| #define PPI_FIELD_TYPE_802_3_EXTENSION ((UCHAR)0x09) |
| |
| #define PPI_FLD_802_3_EXT_FLAG_FCS_PRESENT ((ULONG)0x00000001) |
| |
| typedef struct _PPI_FIELD_802_3_EXTENSION |
| { |
| ULONG Flags; |
| ULONG Errors; |
| } |
| PPI_FIELD_802_3_EXTENSION, *PPPI_FIELD_802_3_EXTENSION; |
| |
| typedef struct _PPI_HEADER |
| { |
| PPI_PACKET_HEADER PacketHeader; |
| PPI_FIELD_HEADER AggregationFieldHeader; |
| PPI_FIELD_AGGREGATION_EXTENSION AggregationField; |
| PPI_FIELD_HEADER Dot3FieldHeader; |
| PPI_FIELD_802_3_EXTENSION Dot3Field; |
| } |
| PPI_HEADER, *PPPI_HEADER; |
| #pragma pack(pop) |
| |
| #ifdef _WIN32 |
| /* |
| * NOTE: this function should be called by the pcap functions that can theoretically |
| * deal with the Tc library for the first time, namely listing the adapters and |
| * opening one. All the other ones (close, read, write, set parameters) work |
| * on an open instance of TC, so we do not care to call this function |
| */ |
| TC_API_LOAD_STATUS LoadTcFunctions(void) |
| { |
| TC_API_LOAD_STATUS currentStatus; |
| |
| do |
| { |
| currentStatus = InterlockedCompareExchange((LONG*)&g_TcFunctions.LoadStatus, TC_API_LOADING, TC_API_UNLOADED); |
| |
| while(currentStatus == TC_API_LOADING) |
| { |
| currentStatus = InterlockedCompareExchange((LONG*)&g_TcFunctions.LoadStatus, TC_API_LOADING, TC_API_LOADING); |
| Sleep(10); |
| } |
| |
| /* |
| * at this point we are either in the LOADED state, unloaded state (i.e. we are the ones loading everything) |
| * or in cannot load |
| */ |
| if(currentStatus == TC_API_LOADED) |
| { |
| return TC_API_LOADED; |
| } |
| |
| if (currentStatus == TC_API_CANNOT_LOAD) |
| { |
| return TC_API_CANNOT_LOAD; |
| } |
| |
| currentStatus = TC_API_CANNOT_LOAD; |
| |
| g_TcFunctions.hTcApiDllHandle = pcap_load_code("TcApi.dll"); |
| if (g_TcFunctions.hTcApiDllHandle == NULL) break; |
| |
| g_TcFunctions.QueryPortList = (TcFcnQueryPortList) pcap_find_function(g_TcFunctions.hTcApiDllHandle, "TcQueryPortList"); |
| g_TcFunctions.FreePortList = (TcFcnFreePortList) pcap_find_function(g_TcFunctions.hTcApiDllHandle, "TcFreePortList"); |
| |
| g_TcFunctions.StatusGetString = (TcFcnStatusGetString) pcap_find_function(g_TcFunctions.hTcApiDllHandle, "TcStatusGetString"); |
| |
| g_TcFunctions.PortGetName = (TcFcnPortGetName) pcap_find_function(g_TcFunctions.hTcApiDllHandle, "TcPortGetName"); |
| g_TcFunctions.PortGetDescription = (TcFcnPortGetDescription) pcap_find_function(g_TcFunctions.hTcApiDllHandle, "TcPortGetDescription"); |
| |
| g_TcFunctions.InstanceOpenByName = (TcFcnInstanceOpenByName) pcap_find_function(g_TcFunctions.hTcApiDllHandle, "TcInstanceOpenByName"); |
| g_TcFunctions.InstanceClose = (TcFcnInstanceClose) pcap_find_function(g_TcFunctions.hTcApiDllHandle, "TcInstanceClose"); |
| g_TcFunctions.InstanceSetFeature = (TcFcnInstanceSetFeature) pcap_find_function(g_TcFunctions.hTcApiDllHandle, "TcInstanceSetFeature"); |
| g_TcFunctions.InstanceQueryFeature = (TcFcnInstanceQueryFeature) pcap_find_function(g_TcFunctions.hTcApiDllHandle, "TcInstanceQueryFeature"); |
| g_TcFunctions.InstanceReceivePackets = (TcFcnInstanceReceivePackets) pcap_find_function(g_TcFunctions.hTcApiDllHandle, "TcInstanceReceivePackets"); |
| g_TcFunctions.InstanceGetReceiveWaitHandle = (TcFcnInstanceGetReceiveWaitHandle)pcap_find_function(g_TcFunctions.hTcApiDllHandle, "TcInstanceGetReceiveWaitHandle"); |
| g_TcFunctions.InstanceTransmitPackets = (TcFcnInstanceTransmitPackets)pcap_find_function(g_TcFunctions.hTcApiDllHandle, "TcInstanceTransmitPackets"); |
| g_TcFunctions.InstanceQueryStatistics = (TcFcnInstanceQueryStatistics)pcap_find_function(g_TcFunctions.hTcApiDllHandle, "TcInstanceQueryStatistics"); |
| |
| g_TcFunctions.PacketsBufferCreate = (TcFcnPacketsBufferCreate) pcap_find_function(g_TcFunctions.hTcApiDllHandle, "TcPacketsBufferCreate"); |
| g_TcFunctions.PacketsBufferDestroy = (TcFcnPacketsBufferDestroy) pcap_find_function(g_TcFunctions.hTcApiDllHandle, "TcPacketsBufferDestroy"); |
| g_TcFunctions.PacketsBufferQueryNextPacket = (TcFcnPacketsBufferQueryNextPacket)pcap_find_function(g_TcFunctions.hTcApiDllHandle, "TcPacketsBufferQueryNextPacket"); |
| g_TcFunctions.PacketsBufferCommitNextPacket = (TcFcnPacketsBufferCommitNextPacket)pcap_find_function(g_TcFunctions.hTcApiDllHandle, "TcPacketsBufferCommitNextPacket"); |
| |
| g_TcFunctions.StatisticsDestroy = (TcFcnStatisticsDestroy) pcap_find_function(g_TcFunctions.hTcApiDllHandle, "TcStatisticsDestroy"); |
| g_TcFunctions.StatisticsUpdate = (TcFcnStatisticsUpdate) pcap_find_function(g_TcFunctions.hTcApiDllHandle, "TcStatisticsUpdate"); |
| g_TcFunctions.StatisticsQueryValue = (TcFcnStatisticsQueryValue) pcap_find_function(g_TcFunctions.hTcApiDllHandle, "TcStatisticsQueryValue"); |
| |
| if ( g_TcFunctions.QueryPortList == NULL |
| || g_TcFunctions.FreePortList == NULL |
| || g_TcFunctions.StatusGetString == NULL |
| || g_TcFunctions.PortGetName == NULL |
| || g_TcFunctions.PortGetDescription == NULL |
| || g_TcFunctions.InstanceOpenByName == NULL |
| || g_TcFunctions.InstanceClose == NULL |
| || g_TcFunctions.InstanceSetFeature == NULL |
| || g_TcFunctions.InstanceQueryFeature == NULL |
| || g_TcFunctions.InstanceReceivePackets == NULL |
| || g_TcFunctions.InstanceGetReceiveWaitHandle == NULL |
| || g_TcFunctions.InstanceTransmitPackets == NULL |
| || g_TcFunctions.InstanceQueryStatistics == NULL |
| || g_TcFunctions.PacketsBufferCreate == NULL |
| || g_TcFunctions.PacketsBufferDestroy == NULL |
| || g_TcFunctions.PacketsBufferQueryNextPacket == NULL |
| || g_TcFunctions.PacketsBufferCommitNextPacket == NULL |
| || g_TcFunctions.StatisticsDestroy == NULL |
| || g_TcFunctions.StatisticsUpdate == NULL |
| || g_TcFunctions.StatisticsQueryValue == NULL |
| ) |
| { |
| break; |
| } |
| |
| /* |
| * everything got loaded, yay!! |
| */ |
| currentStatus = TC_API_LOADED; |
| }while(FALSE); |
| |
| if (currentStatus != TC_API_LOADED) |
| { |
| if (g_TcFunctions.hTcApiDllHandle != NULL) |
| { |
| FreeLibrary(g_TcFunctions.hTcApiDllHandle); |
| g_TcFunctions.hTcApiDllHandle = NULL; |
| } |
| } |
| |
| InterlockedExchange((LONG*)&g_TcFunctions.LoadStatus, currentStatus); |
| |
| return currentStatus; |
| } |
| #else |
| // static linking |
| TC_API_LOAD_STATUS LoadTcFunctions(void) |
| { |
| return TC_API_LOADED; |
| } |
| #endif |
| |
| /* |
| * Private data for capturing on TurboCap devices. |
| */ |
| struct pcap_tc { |
| TC_INSTANCE TcInstance; |
| TC_PACKETS_BUFFER TcPacketsBuffer; |
| ULONG TcAcceptedCount; |
| u_char *PpiPacket; |
| }; |
| |
| int |
| TcFindAllDevs(pcap_if_list_t *devlist, char *errbuf) |
| { |
| TC_API_LOAD_STATUS loadStatus; |
| ULONG numPorts; |
| PTC_PORT pPorts = NULL; |
| TC_STATUS status; |
| int result = 0; |
| pcap_if_t *dev; |
| ULONG i; |
| |
| do |
| { |
| loadStatus = LoadTcFunctions(); |
| |
| if (loadStatus != TC_API_LOADED) |
| { |
| result = 0; |
| break; |
| } |
| |
| /* |
| * enumerate the ports, and add them to the list |
| */ |
| status = g_TcFunctions.QueryPortList(&pPorts, &numPorts); |
| |
| if (status != TC_SUCCESS) |
| { |
| result = 0; |
| break; |
| } |
| |
| for (i = 0; i < numPorts; i++) |
| { |
| /* |
| * transform the port into an entry in the list |
| */ |
| dev = TcCreatePcapIfFromPort(pPorts[i]); |
| |
| if (dev != NULL) |
| add_dev(devlist, dev->name, dev->flags, dev->description, errbuf); |
| } |
| |
| if (numPorts > 0) |
| { |
| /* |
| * ignore the result here |
| */ |
| status = g_TcFunctions.FreePortList(pPorts); |
| } |
| |
| }while(FALSE); |
| |
| return result; |
| } |
| |
| static pcap_if_t* TcCreatePcapIfFromPort(TC_PORT port) |
| { |
| CHAR *name; |
| CHAR *description; |
| pcap_if_t *newIf = NULL; |
| |
| newIf = (pcap_if_t*)malloc(sizeof(*newIf)); |
| if (newIf == NULL) |
| { |
| return NULL; |
| } |
| |
| memset(newIf, 0, sizeof(*newIf)); |
| |
| name = g_TcFunctions.PortGetName(port); |
| description = g_TcFunctions.PortGetDescription(port); |
| |
| newIf->name = (char*)malloc(strlen(name) + 1); |
| if (newIf->name == NULL) |
| { |
| free(newIf); |
| return NULL; |
| } |
| |
| newIf->description = (char*)malloc(strlen(description) + 1); |
| if (newIf->description == NULL) |
| { |
| free(newIf->name); |
| free(newIf); |
| return NULL; |
| } |
| |
| strcpy(newIf->name, name); |
| strcpy(newIf->description, description); |
| |
| newIf->addresses = NULL; |
| newIf->next = NULL; |
| newIf->flags = 0; |
| |
| return newIf; |
| |
| } |
| |
| static int |
| TcActivate(pcap_t *p) |
| { |
| struct pcap_tc *pt = p->priv; |
| TC_STATUS status; |
| ULONG timeout; |
| PPPI_HEADER pPpiHeader; |
| |
| if (p->opt.rfmon) |
| { |
| /* |
| * No monitor mode on Tc cards; they're Ethernet |
| * capture adapters. |
| */ |
| return PCAP_ERROR_RFMON_NOTSUP; |
| } |
| |
| pt->PpiPacket = malloc(sizeof(PPI_HEADER) + MAX_TC_PACKET_SIZE); |
| |
| if (pt->PpiPacket == NULL) |
| { |
| snprintf(p->errbuf, PCAP_ERRBUF_SIZE, "Error allocating memory"); |
| 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; |
| |
| /* |
| * Initialize the PPI fixed fields |
| */ |
| pPpiHeader = (PPPI_HEADER)pt->PpiPacket; |
| pPpiHeader->PacketHeader.PphDlt = DLT_EN10MB; |
| pPpiHeader->PacketHeader.PphLength = sizeof(PPI_HEADER); |
| pPpiHeader->PacketHeader.PphFlags = 0; |
| pPpiHeader->PacketHeader.PphVersion = 0; |
| |
| pPpiHeader->AggregationFieldHeader.PfhLength = sizeof(PPI_FIELD_AGGREGATION_EXTENSION); |
| pPpiHeader->AggregationFieldHeader.PfhType = PPI_FIELD_TYPE_AGGREGATION_EXTENSION; |
| |
| pPpiHeader->Dot3FieldHeader.PfhLength = sizeof(PPI_FIELD_802_3_EXTENSION); |
| pPpiHeader->Dot3FieldHeader.PfhType = PPI_FIELD_TYPE_802_3_EXTENSION; |
| |
| status = g_TcFunctions.InstanceOpenByName(p->opt.device, &pt->TcInstance); |
| |
| if (status != TC_SUCCESS) |
| { |
| /* Adapter detected but we are not able to open it. Return failure. */ |
| snprintf(p->errbuf, PCAP_ERRBUF_SIZE, "Error opening TurboCap adapter: %s", g_TcFunctions.StatusGetString(status)); |
| return PCAP_ERROR; |
| } |
| |
| p->linktype = DLT_EN10MB; |
| p->dlt_list = (u_int *) malloc(sizeof(u_int) * 2); |
| /* |
| * If that fails, just leave the list empty. |
| */ |
| if (p->dlt_list != NULL) { |
| p->dlt_list[0] = DLT_EN10MB; |
| p->dlt_list[1] = DLT_PPI; |
| p->dlt_count = 2; |
| } |
| |
| /* |
| * ignore promiscuous mode |
| * p->opt.promisc |
| */ |
| |
| |
| /* |
| * ignore all the buffer sizes |
| */ |
| |
| /* |
| * enable reception |
| */ |
| status = g_TcFunctions.InstanceSetFeature(pt->TcInstance, TC_INST_FT_RX_STATUS, 1); |
| |
| if (status != TC_SUCCESS) |
| { |
| snprintf(p->errbuf, PCAP_ERRBUF_SIZE,"Error enabling reception on a TurboCap instance: %s", g_TcFunctions.StatusGetString(status)); |
| goto bad; |
| } |
| |
| /* |
| * enable transmission |
| */ |
| status = g_TcFunctions.InstanceSetFeature(pt->TcInstance, TC_INST_FT_TX_STATUS, 1); |
| /* |
| * Ignore the error here. |
| */ |
| |
| p->inject_op = TcInject; |
| /* |
| * if the timeout is -1, it means immediate return, no timeout |
| * if the timeout is 0, it means INFINITE |
| */ |
| |
| if (p->opt.timeout == 0) |
| { |
| timeout = 0xFFFFFFFF; |
| } |
| else |
| if (p->opt.timeout < 0) |
| { |
| /* |
| * we insert a minimal timeout here |
| */ |
| timeout = 10; |
| } |
| else |
| { |
| timeout = p->opt.timeout; |
| } |
| |
| status = g_TcFunctions.InstanceSetFeature(pt->TcInstance, TC_INST_FT_READ_TIMEOUT, timeout); |
| |
| if (status != TC_SUCCESS) |
| { |
| snprintf(p->errbuf, PCAP_ERRBUF_SIZE,"Error setting the read timeout a TurboCap instance: %s", g_TcFunctions.StatusGetString(status)); |
| goto bad; |
| } |
| |
| p->read_op = TcRead; |
| p->setfilter_op = install_bpf_program; |
| p->setdirection_op = NULL; /* Not implemented. */ |
| p->set_datalink_op = TcSetDatalink; |
| p->getnonblock_op = TcGetNonBlock; |
| p->setnonblock_op = TcSetNonBlock; |
| p->stats_op = TcStats; |
| #ifdef _WIN32 |
| p->stats_ex_op = TcStatsEx; |
| p->setbuff_op = TcSetBuff; |
| p->setmode_op = TcSetMode; |
| p->setmintocopy_op = TcSetMinToCopy; |
| p->getevent_op = TcGetReceiveWaitHandle; |
| p->oid_get_request_op = TcOidGetRequest; |
| p->oid_set_request_op = TcOidSetRequest; |
| p->sendqueue_transmit_op = TcSendqueueTransmit; |
| p->setuserbuffer_op = TcSetUserBuffer; |
| p->live_dump_op = TcLiveDump; |
| p->live_dump_ended_op = TcLiveDumpEnded; |
| p->get_airpcap_handle_op = TcGetAirPcapHandle; |
| #else |
| p->selectable_fd = -1; |
| #endif |
| |
| p->cleanup_op = TcCleanup; |
| |
| return 0; |
| bad: |
| TcCleanup(p); |
| return PCAP_ERROR; |
| } |
| |
| pcap_t * |
| TcCreate(const char *device, char *ebuf, int *is_ours) |
| { |
| ULONG numPorts; |
| PTC_PORT pPorts = NULL; |
| TC_STATUS status; |
| int is_tc; |
| ULONG i; |
| pcap_t *p; |
| |
| if (LoadTcFunctions() != TC_API_LOADED) |
| { |
| /* |
| * XXX - report this as an error rather than as |
| * "not a TurboCap device"? |
| */ |
| *is_ours = 0; |
| return NULL; |
| } |
| |
| /* |
| * enumerate the ports, and add them to the list |
| */ |
| status = g_TcFunctions.QueryPortList(&pPorts, &numPorts); |
| |
| if (status != TC_SUCCESS) |
| { |
| /* |
| * XXX - report this as an error rather than as |
| * "not a TurboCap device"? |
| */ |
| *is_ours = 0; |
| return NULL; |
| } |
| |
| is_tc = FALSE; |
| for (i = 0; i < numPorts; i++) |
| { |
| if (strcmp(g_TcFunctions.PortGetName(pPorts[i]), device) == 0) |
| { |
| is_tc = TRUE; |
| break; |
| } |
| } |
| |
| if (numPorts > 0) |
| { |
| /* |
| * ignore the result here |
| */ |
| (void)g_TcFunctions.FreePortList(pPorts); |
| } |
| |
| if (!is_tc) |
| { |
| *is_ours = 0; |
| return NULL; |
| } |
| |
| /* OK, it's probably ours. */ |
| *is_ours = 1; |
| |
| p = PCAP_CREATE_COMMON(ebuf, struct pcap_tc); |
| if (p == NULL) |
| return NULL; |
| |
| p->activate_op = TcActivate; |
| /* |
| * Set these up front, so that, even if our client tries |
| * to set non-blocking mode before we're activated, or |
| * query the state of non-blocking mode, they get an error, |
| * rather than having the non-blocking mode option set |
| * for use later. |
| */ |
| p->getnonblock_op = TcGetNonBlock; |
| p->setnonblock_op = TcSetNonBlock; |
| return p; |
| } |
| |
| static int TcSetDatalink(pcap_t *p, int dlt) |
| { |
| /* |
| * We don't have to do any work here; pcap_set_datalink() checks |
| * whether the value is in the list of DLT_ values we |
| * supplied, so we don't have to, and, if it is valid, sets |
| * p->linktype to the new value; we don't have to do anything |
| * in hardware, we just use what's in p->linktype. |
| * |
| * We do have to have a routine, however, so that pcap_set_datalink() |
| * doesn't think we don't support setting the link-layer header |
| * type at all. |
| */ |
| return 0; |
| } |
| |
| static int TcGetNonBlock(pcap_t *p) |
| { |
| snprintf(p->errbuf, PCAP_ERRBUF_SIZE, |
| "Non-blocking mode isn't supported for TurboCap ports"); |
| return -1; |
| } |
| |
| static int TcSetNonBlock(pcap_t *p, int nonblock) |
| { |
| snprintf(p->errbuf, PCAP_ERRBUF_SIZE, |
| "Non-blocking mode isn't supported for TurboCap ports"); |
| return -1; |
| } |
| |
| static void TcCleanup(pcap_t *p) |
| { |
| struct pcap_tc *pt = p->priv; |
| |
| if (pt->TcPacketsBuffer != NULL) |
| { |
| g_TcFunctions.PacketsBufferDestroy(pt->TcPacketsBuffer); |
| pt->TcPacketsBuffer = NULL; |
| } |
| if (pt->TcInstance != NULL) |
| { |
| /* |
| * here we do not check for the error values |
| */ |
| g_TcFunctions.InstanceClose(pt->TcInstance); |
| pt->TcInstance = NULL; |
| } |
| |
| if (pt->PpiPacket != NULL) |
| { |
| free(pt->PpiPacket); |
| pt->PpiPacket = NULL; |
| } |
| |
| pcap_cleanup_live_common(p); |
| } |
| |
| /* Send a packet to the network */ |
| static int TcInject(pcap_t *p, const void *buf, int size) |
| { |
| struct pcap_tc *pt = p->priv; |
| TC_STATUS status; |
| TC_PACKETS_BUFFER buffer; |
| TC_PACKET_HEADER header; |
| |
| if (size >= 0xFFFF) |
| { |
| snprintf(p->errbuf, PCAP_ERRBUF_SIZE, "send error: the TurboCap API does not support packets larger than 64k"); |
| return -1; |
| } |
| |
| status = g_TcFunctions.PacketsBufferCreate(sizeof(TC_PACKET_HEADER) + TC_ALIGN_USHORT_TO_64BIT((USHORT)size), &buffer); |
| |
| if (status != TC_SUCCESS) |
| { |
| snprintf(p->errbuf, PCAP_ERRBUF_SIZE, "send error: TcPacketsBufferCreate failure: %s (%08x)", g_TcFunctions.StatusGetString(status), status); |
| return -1; |
| } |
| |
| /* |
| * we assume that the packet is without the checksum, as common with WinPcap |
| */ |
| memset(&header, 0, sizeof(header)); |
| |
| header.Length = (USHORT)size; |
| header.CapturedLength = header.Length; |
| |
| status = g_TcFunctions.PacketsBufferCommitNextPacket(buffer, &header, (PVOID)buf); |
| |
| if (status == TC_SUCCESS) |
| { |
| status = g_TcFunctions.InstanceTransmitPackets(pt->TcInstance, buffer); |
| |
| if (status != TC_SUCCESS) |
| { |
| snprintf(p->errbuf, PCAP_ERRBUF_SIZE, "send error: TcInstanceTransmitPackets failure: %s (%08x)", g_TcFunctions.StatusGetString(status), status); |
| } |
| } |
| else |
| { |
| snprintf(p->errbuf, PCAP_ERRBUF_SIZE, "send error: TcPacketsBufferCommitNextPacket failure: %s (%08x)", g_TcFunctions.StatusGetString(status), status); |
| } |
| |
| g_TcFunctions.PacketsBufferDestroy(buffer); |
| |
| if (status != TC_SUCCESS) |
| { |
| return -1; |
| } |
| else |
| { |
| return 0; |
| } |
| } |
| |
| static int TcRead(pcap_t *p, int cnt, pcap_handler callback, u_char *user) |
| { |
| struct pcap_tc *pt = p->priv; |
| TC_STATUS status; |
| int n = 0; |
| |
| /* |
| * Has "pcap_breakloop()" been called? |
| */ |
| if (p->break_loop) |
| { |
| /* |
| * Yes - clear the flag that indicates that it |
| * has, and return -2 to indicate that we were |
| * told to break out of the loop. |
| */ |
| p->break_loop = 0; |
| return -2; |
| } |
| |
| if (pt->TcPacketsBuffer == NULL) |
| { |
| status = g_TcFunctions.InstanceReceivePackets(pt->TcInstance, &pt->TcPacketsBuffer); |
| if (status != TC_SUCCESS) |
| { |
| snprintf(p->errbuf, PCAP_ERRBUF_SIZE, "read error, TcInstanceReceivePackets failure: %s (%08x)", g_TcFunctions.StatusGetString(status), status); |
| return -1; |
| } |
| } |
| |
| while (TRUE) |
| { |
| struct pcap_pkthdr hdr; |
| TC_PACKET_HEADER tcHeader; |
| PVOID data; |
| ULONG filterResult; |
| |
| /* |
| * Has "pcap_breakloop()" been called? |
| * If so, return immediately - if we haven't read any |
| * packets, clear the flag and return -2 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 -2; |
| } |
| else |
| { |
| return n; |
| } |
| } |
| |
| if (pt->TcPacketsBuffer == NULL) |
| { |
| break; |
| } |
| |
| status = g_TcFunctions.PacketsBufferQueryNextPacket(pt->TcPacketsBuffer, &tcHeader, &data); |
| |
| if (status == TC_ERROR_END_OF_BUFFER) |
| { |
| g_TcFunctions.PacketsBufferDestroy(pt->TcPacketsBuffer); |
| pt->TcPacketsBuffer = NULL; |
| break; |
| } |
| |
| if (status != TC_SUCCESS) |
| { |
| snprintf(p->errbuf, PCAP_ERRBUF_SIZE, "read error, TcPacketsBufferQueryNextPacket failure: %s (%08x)", g_TcFunctions.StatusGetString(status), status); |
| return -1; |
| } |
| |
| /* No underlying filtering system. We need to filter on our own */ |
| if (p->fcode.bf_insns) |
| { |
| filterResult = pcap_filter(p->fcode.bf_insns, data, tcHeader.Length, tcHeader.CapturedLength); |
| |
| if (filterResult == 0) |
| { |
| continue; |
| } |
| |
| if (filterResult > tcHeader.CapturedLength) |
| { |
| filterResult = tcHeader.CapturedLength; |
| } |
| } |
| else |
| { |
| filterResult = tcHeader.CapturedLength; |
| } |
| |
| pt->TcAcceptedCount ++; |
| |
| hdr.ts.tv_sec = (bpf_u_int32)(tcHeader.Timestamp / (ULONGLONG)(1000 * 1000 * 1000)); |
| hdr.ts.tv_usec = (bpf_u_int32)((tcHeader.Timestamp % (ULONGLONG)(1000 * 1000 * 1000)) / 1000); |
| |
| if (p->linktype == DLT_EN10MB) |
| { |
| hdr.caplen = filterResult; |
| hdr.len = tcHeader.Length; |
| (*callback)(user, &hdr, data); |
| } |
| else |
| { |
| PPPI_HEADER pPpiHeader = (PPPI_HEADER)pt->PpiPacket; |
| PVOID data2 = pPpiHeader + 1; |
| |
| pPpiHeader->AggregationField.InterfaceId = TC_PH_FLAGS_RX_PORT_ID(tcHeader.Flags); |
| pPpiHeader->Dot3Field.Errors = tcHeader.Errors; |
| if (tcHeader.Flags & TC_PH_FLAGS_CHECKSUM) |
| { |
| pPpiHeader->Dot3Field.Flags = PPI_FLD_802_3_EXT_FLAG_FCS_PRESENT; |
| } |
| else |
| { |
| pPpiHeader->Dot3Field.Flags = 0; |
| } |
| |
| if (filterResult <= MAX_TC_PACKET_SIZE) |
| { |
| memcpy(data2, data, filterResult); |
| hdr.caplen = sizeof(PPI_HEADER) + filterResult; |
| hdr.len = sizeof(PPI_HEADER) + tcHeader.Length; |
| } |
| else |
| { |
| memcpy(data2, data, MAX_TC_PACKET_SIZE); |
| hdr.caplen = sizeof(PPI_HEADER) + MAX_TC_PACKET_SIZE; |
| hdr.len = sizeof(PPI_HEADER) + tcHeader.Length; |
| } |
| |
| (*callback)(user, &hdr, pt->PpiPacket); |
| |
| } |
| |
| if (++n >= cnt && cnt > 0) |
| { |
| return n; |
| } |
| } |
| |
| return n; |
| } |
| |
| static int |
| TcStats(pcap_t *p, struct pcap_stat *ps) |
| { |
| struct pcap_tc *pt = p->priv; |
| TC_STATISTICS statistics; |
| TC_STATUS status; |
| ULONGLONG counter; |
| struct pcap_stat s; |
| |
| status = g_TcFunctions.InstanceQueryStatistics(pt->TcInstance, &statistics); |
| |
| if (status != TC_SUCCESS) |
| { |
| snprintf(p->errbuf, PCAP_ERRBUF_SIZE, "TurboCap error in TcInstanceQueryStatistics: %s (%08x)", g_TcFunctions.StatusGetString(status), status); |
| return -1; |
| } |
| |
| memset(&s, 0, sizeof(s)); |
| |
| status = g_TcFunctions.StatisticsQueryValue(statistics, TC_COUNTER_INSTANCE_TOTAL_RX_PACKETS, &counter); |
| if (status != TC_SUCCESS) |
| { |
| snprintf(p->errbuf, PCAP_ERRBUF_SIZE, "TurboCap error in TcStatisticsQueryValue: %s (%08x)", g_TcFunctions.StatusGetString(status), status); |
| return -1; |
| } |
| if (counter <= (ULONGLONG)0xFFFFFFFF) |
| { |
| s.ps_recv = (ULONG)counter; |
| } |
| else |
| { |
| s.ps_recv = 0xFFFFFFFF; |
| } |
| |
| status = g_TcFunctions.StatisticsQueryValue(statistics, TC_COUNTER_INSTANCE_RX_DROPPED_PACKETS, &counter); |
| if (status != TC_SUCCESS) |
| { |
| snprintf(p->errbuf, PCAP_ERRBUF_SIZE, "TurboCap error in TcStatisticsQueryValue: %s (%08x)", g_TcFunctions.StatusGetString(status), status); |
| return -1; |
| } |
| if (counter <= (ULONGLONG)0xFFFFFFFF) |
| { |
| s.ps_ifdrop = (ULONG)counter; |
| s.ps_drop = (ULONG)counter; |
| } |
| else |
| { |
| s.ps_ifdrop = 0xFFFFFFFF; |
| s.ps_drop = 0xFFFFFFFF; |
| } |
| |
| #if defined(_WIN32) && defined(ENABLE_REMOTE) |
| s.ps_capt = pt->TcAcceptedCount; |
| #endif |
| *ps = s; |
| |
| return 0; |
| } |
| |
| |
| #ifdef _WIN32 |
| static struct pcap_stat * |
| TcStatsEx(pcap_t *p, int *pcap_stat_size) |
| { |
| struct pcap_tc *pt = p->priv; |
| TC_STATISTICS statistics; |
| TC_STATUS status; |
| ULONGLONG counter; |
| |
| *pcap_stat_size = sizeof (p->stat); |
| |
| status = g_TcFunctions.InstanceQueryStatistics(pt->TcInstance, &statistics); |
| |
| if (status != TC_SUCCESS) |
| { |
| snprintf(p->errbuf, PCAP_ERRBUF_SIZE, "TurboCap error in TcInstanceQueryStatistics: %s (%08x)", g_TcFunctions.StatusGetString(status), status); |
| return NULL; |
| } |
| |
| memset(&p->stat, 0, sizeof(p->stat)); |
| |
| status = g_TcFunctions.StatisticsQueryValue(statistics, TC_COUNTER_INSTANCE_TOTAL_RX_PACKETS, &counter); |
| if (status != TC_SUCCESS) |
| { |
| snprintf(p->errbuf, PCAP_ERRBUF_SIZE, "TurboCap error in TcStatisticsQueryValue: %s (%08x)", g_TcFunctions.StatusGetString(status), status); |
| return NULL; |
| } |
| if (counter <= (ULONGLONG)0xFFFFFFFF) |
| { |
| p->stat.ps_recv = (ULONG)counter; |
| } |
| else |
| { |
| p->stat.ps_recv = 0xFFFFFFFF; |
| } |
| |
| status = g_TcFunctions.StatisticsQueryValue(statistics, TC_COUNTER_INSTANCE_RX_DROPPED_PACKETS, &counter); |
| if (status != TC_SUCCESS) |
| { |
| snprintf(p->errbuf, PCAP_ERRBUF_SIZE, "TurboCap error in TcStatisticsQueryValue: %s (%08x)", g_TcFunctions.StatusGetString(status), status); |
| return NULL; |
| } |
| if (counter <= (ULONGLONG)0xFFFFFFFF) |
| { |
| p->stat.ps_ifdrop = (ULONG)counter; |
| p->stat.ps_drop = (ULONG)counter; |
| } |
| else |
| { |
| p->stat.ps_ifdrop = 0xFFFFFFFF; |
| p->stat.ps_drop = 0xFFFFFFFF; |
| } |
| |
| #if defined(_WIN32) && defined(ENABLE_REMOTE) |
| p->stat.ps_capt = pt->TcAcceptedCount; |
| #endif |
| |
| return &p->stat; |
| } |
| |
| /* Set the dimension of the kernel-level capture buffer */ |
| static int |
| TcSetBuff(pcap_t *p, int dim) |
| { |
| /* |
| * XXX turbocap has an internal way of managing buffers. |
| * And at the moment it's not configurable, so we just |
| * silently ignore the request to set the buffer. |
| */ |
| return 0; |
| } |
| |
| static int |
| TcSetMode(pcap_t *p, int mode) |
| { |
| if (mode != MODE_CAPT) |
| { |
| snprintf(p->errbuf, PCAP_ERRBUF_SIZE, "Mode %d not supported by TurboCap devices. TurboCap only supports capture.", mode); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static int |
| TcSetMinToCopy(pcap_t *p, int size) |
| { |
| struct pcap_tc *pt = p->priv; |
| TC_STATUS status; |
| |
| if (size < 0) |
| { |
| snprintf(p->errbuf, PCAP_ERRBUF_SIZE, "Mintocopy cannot be less than 0."); |
| return -1; |
| } |
| |
| status = g_TcFunctions.InstanceSetFeature(pt->TcInstance, TC_INST_FT_MINTOCOPY, (ULONG)size); |
| |
| if (status != TC_SUCCESS) |
| { |
| snprintf(p->errbuf, PCAP_ERRBUF_SIZE, "TurboCap error setting the mintocopy: %s (%08x)", g_TcFunctions.StatusGetString(status), status); |
| } |
| |
| return 0; |
| } |
| |
| static HANDLE |
| TcGetReceiveWaitHandle(pcap_t *p) |
| { |
| struct pcap_tc *pt = p->priv; |
| |
| return g_TcFunctions.InstanceGetReceiveWaitHandle(pt->TcInstance); |
| } |
| |
| static int |
| TcOidGetRequest(pcap_t *p, bpf_u_int32 oid _U_, void *data _U_, size_t *lenp _U_) |
| { |
| snprintf(p->errbuf, PCAP_ERRBUF_SIZE, |
| "An OID get request cannot be performed on a TurboCap device"); |
| return PCAP_ERROR; |
| } |
| |
| static int |
| TcOidSetRequest(pcap_t *p, bpf_u_int32 oid _U_, const void *data _U_, |
| size_t *lenp _U_) |
| { |
| snprintf(p->errbuf, PCAP_ERRBUF_SIZE, |
| "An OID set request cannot be performed on a TurboCap device"); |
| return PCAP_ERROR; |
| } |
| |
| static u_int |
| TcSendqueueTransmit(pcap_t *p, pcap_send_queue *queue _U_, int sync _U_) |
| { |
| snprintf(p->errbuf, PCAP_ERRBUF_SIZE, |
| "Packets cannot be bulk transmitted on a TurboCap device"); |
| return 0; |
| } |
| |
| static int |
| TcSetUserBuffer(pcap_t *p, int size _U_) |
| { |
| snprintf(p->errbuf, PCAP_ERRBUF_SIZE, |
| "The user buffer cannot be set on a TurboCap device"); |
| return -1; |
| } |
| |
| static int |
| TcLiveDump(pcap_t *p, char *filename _U_, int maxsize _U_, int maxpacks _U_) |
| { |
| snprintf(p->errbuf, PCAP_ERRBUF_SIZE, |
| "Live packet dumping cannot be performed on a TurboCap device"); |
| return -1; |
| } |
| |
| static int |
| TcLiveDumpEnded(pcap_t *p, int sync _U_) |
| { |
| snprintf(p->errbuf, PCAP_ERRBUF_SIZE, |
| "Live packet dumping cannot be performed on a TurboCap device"); |
| return -1; |
| } |
| |
| static PAirpcapHandle |
| TcGetAirPcapHandle(pcap_t *p _U_) |
| { |
| return NULL; |
| } |
| #endif |