// $Id: linux_adapter.c,v 1.33 2016/02/17 01:32:01 lpoulsen Exp $
// Copyright (C) 2001 Wi_LAN Technologies, Inc
//
// $Log: linux_adapter.c,v $
// Revision 1.33  2016/02/17 01:32:01  lpoulsen
// Fix an error in the jumbo packet fix.
//
// Revision 1.32  2016/02/11 01:11:17  lpoulsen
// Silently discard oversized ethernet packets unless "debug nethealth"
//     is set. With "debug nethealth", dump items to help figure out who
//     is sending these packets:
//     - enet dest and src addresses
//     - enet packet type
//     - first 32 bytes of payload
//     - decoded IPv4 src and dest addresses
// It looks like machines with Gigabit ethernet cards are sending many
//     jumbo packets.
//
// Revision 1.31  2012/05/08 18:24:00  lpoulsen
// Compile all modules with -pthread on Linux
// Clean up all compiler warnings
// Count packets in OUT OF MEMORY message
//
// Revision 1.30  2012/04/19 20:19:12  lpoulsen
// Error message instead of SIGSEG when running out of memory at startup
//
// Revision 1.29  2011/10/07 18:07:02  lpoulsen
// On system with just one ethernet port, don't require user to enter "1"
//
// Revision 1.28  2011/08/29 22:29:27  lpoulsen
// Clean compilation on FC3 linux.
//
// Revision 1.27  2011/07/08 21:50:30  lpoulsen
// Eliminate (almost all) use of debug_level, use debug_flags instead.
//
// Revision 1.26  2011/02/21 22:26:36  lpoulsen
// "adapter" command works on Linux
//
// Revision 1.25  2011/02/08 22:07:02  lpoulsen
// evived the LINUX build of ECON - now ECON-PLUS
//
// Revision 1.24  2010/12/22 17:36:34  lpoulsen
// Select/Deselect list of ranges
// Expand to up to 511 radios
// Protocol fixes
//
// Revision 1.23  2008/07/17 00:14:37  lpoulsen
// Exit gracefully on EOF on stdin.
// CVS ----------------------------------------------------------------------
//
// Revision 1.22  2008/07/11 20:14:10  lpoulsen
// Resolve many compiler warnings on Linux
//
// Revision 1.21  2008/04/26 00:48:25  lpoulsen
// Rename selectedAdapterId global var to option_C
//
// Revision 1.20  2006/04/28 02:14:17  lpoulsen
// Finally works with the new libnet (1.1.2.1)
//
// Revision 1.19  2006/02/21 00:48:49  lpoulsen
// Update to work with libnet 1.1 - much easier and modern.
//
// Revision 1.18  2005/06/25 00:19:30  lpoulsen
// Move pcap from thread to main program.
//
// Revision 1.17  2005/06/24 22:29:04  lpoulsen
// Correct an off-by-one in OpenBSD get_adapters()
//
// Revision 1.16  2005/06/24 21:47:06  lpoulsen
// get_adapter() versions for OpenBSD *and* Linux. Works on Linux.
//
// Revision 1.15  2003/08/25 20:43:32  lpoulsen
// Correctly handle queue overflow on busy network.
//
// Revision 1.14  2003/08/25 20:16:46  lpoulsen
// Clean up exit for FileThread ...
//
// Revision 1.13  2003/08/25 18:09:04  lpoulsen
// Move pcap_next() reading calls to a separate thread
//     in order to prevent getting stuck when radios are the only
//     traffic on the ethernet segment.
//
// Revision 1.12  2003/03/11 01:14:13  lpoulsen
// Correct compile errors found in Calgary.
//
// Revision 1.11  2002/10/16 16:59:47  mourlouk
// Improved way of retrieving MAC addresses - VAL support
//
// Revision 1.9  2002/05/21 17:53:50  lpoulsen
// Fix many compiler warnings.
//
// Revision 1.8  2002/05/21 17:52:25  lpoulsen
// Fix compiler warning with a cast.
//
// Revision 1.7  2001/11/30 21:43:31  lpoulsen
// Cleanup after extract_adapter_name()
//
// Revision 1.6  2001/11/30 19:30:39  lpoulsen
// Fix typo in variable name.
//
// Revision 1.5  2001/11/30 19:27:46  lpoulsen
// Allow "econ -c 1" to specify adapter #1 on command line.
//
// Revision 1.4  2001/11/12 22:52:32  mourlouk
// making compiler happy (I)
//
// Revision 1.3  2001/11/08 23:56:14  mourlouk
// Changed linux_adapter.c so you can see the MAC + IP address of
//     each ethernet adapter
//
// Revision 1.2  2001/10/30 01:07:45  lpoulsen
// Add "module ID" statement, audit trail generator and copyright line
// Remove "win_or_linux.h" and use a -D in Linux makefile instead.
//
//
#include <stdlib.h>
#include <sys/types.h>
#include <stdio.h>
#include <sys/socket.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <errno.h>
#include <netinet/in.h>
#include <netinet/if_ether.h> /* includes net/ethernet.h */
#include <pcap.h>             // libpcap
#include <libnet.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <time.h>
#include "afartype.h"
#include "epacket.h"
#include "adapter.h"
#include "util.h"
#include "ucon.h"     // option_C
#include <pthread.h>
#include <assert.h>

#ifdef AF_LINK        // OpenBSD - use getifaddrs()
#include <net/if_dl.h>
#include <ifaddrs.h>
#endif

#define PCAP_BUFSIZE 1520

char     *dev; /* name of the device to use */
libnet_t *lh;
pcap_t   *descr;
char    tmpAdapterNameFile[]="adpt013";
char    dev_name[48];
char    devaddr[48];
#define MAX_LOCAL_HOST_NAME 32
char    localHostName[MAX_LOCAL_HOST_NAME];

macAddr my_mac_address = { 0, 0, 0, 0, 0, 0 };

struct adapter_struct ethernet_adapters[Max_Num_Adapter+1];

#ifdef PCAP_THREADED
pthread_t pcap_proc;

typedef struct pcap_buffer {
    struct pcap_buffer *next;
    size_t        size;
    unsigned char ether_packet[1520];
} pcap_buffer;

typedef struct {
    pcap_buffer     *head;
    pcap_buffer     *tail;
    pthread_mutex_t  lock;
    size_t           count;
} pcap_queue;

pcap_queue pcap_buffer_queue = {NULL, NULL, PTHREAD_MUTEX_INITIALIZER, 0};
pcap_queue pcap_free_queue   = {NULL, NULL, PTHREAD_MUTEX_INITIALIZER, 0};

extern char *format_ip_addr(ip_addr *ipa);

void
pcap_enq(pcap_queue *q, pcap_buffer *b)
{
    if (b == NULL) {
        printf("error: pcap_enq() called with NULL buffer ptr!\n");
        return;
    }
    b->next = NULL;

    pthread_mutex_lock(&q->lock);

    if (q->head == NULL) {
        q->head = b;
        q->tail = b;
        q->count = 1;
    } else {
        q->tail->next = b;
        q->tail = b;
        q->count++;
    }
    pthread_mutex_unlock(&q->lock);
}

pcap_buffer *
pcap_deq(pcap_queue *q)
{
    pcap_buffer *pcb;

    if (q->head == NULL)
        return NULL;
    pthread_mutex_lock(&q->lock);
    pcb = q->head;
    if (pcb) {
        q->head = pcb->next;
        q->count--;
        if (q->count == 0) {
            assert(q->tail == pcb);
            q->tail = NULL;
        }
    }
    pthread_mutex_unlock(&q->lock);
    return pcb;
}

void pcap_reader(pcap_t *descr)
{
    int                 i;
    pcap_buffer        *pcb;
    u_char             *pkt_data;
#ifdef USE_NEXT_EX
    int                 status;
    struct pcap_pkthdr *pkt_header;     /* pcap.h */
#else
    struct pcap_pkthdr  hdr;
    struct pcap_pkthdr *pkt_header = &hdr;
#endif
    static int pkts_rcvd = 0;

    pthread_mutex_init(&pcap_buffer_queue.lock, NULL);
    pthread_mutex_init(&pcap_free_queue.lock, NULL);

    // printf("Starting PCAP reader process ");
    for (i = 0; i < 1000; i++) {
        pcb = malloc(sizeof(struct pcap_buffer));
        if (!pcb) {
            printf("error: pcap_reader() startup got only %i buffers\n", i);
            break;
        }
        pcap_enq(&pcap_free_queue, pcb);
    }
    // printf("...\n");
    while(1) {
        if (econ_phase == ECON_CLOSE_ADAPTER)
            return;
#ifdef USE_NEXT_EX
        status = pcap_next_ex(descr, &pkt_header, &pkt_data);
        switch (status)
        {
        case -1: // Error
        case -2: // Eof
            printf("adapter_receive failed\n");
            exit(1);
        case 0: // Timeout
            continue;
        case 1: // Normal - fall through
            ;
        }
#else
        pkt_data = (void *)pcap_next(descr, pkt_header);
        if (!pkt_data)
            continue;
#endif
        if ((pkt_header->len > 1518) // MAX_ENET_PACKET_SIZE
             || (pkt_header->len != pkt_header->caplen))
        {
            int i;

            if (debug_flags & DBFLG_NETHEALTH) {
                printf("Bad packet length %d/%d\n",
                       pkt_header->len, pkt_header->caplen);
                printf("    Packet header begins:\n        ");
                for (i = 0; i < 14; i++) {
                    printf("%02X ", pkt_data[i]);
                    if ((i == 5) || (i == 11))
                        printf("  ");
                }
                printf("\n");
                for (i = 14; i < 46; i++) {
                    if (((i - 14) % 16) == 0)
                        printf("        ");
                    printf("%02X ", pkt_data[i]);
                    if (((i - 14) % 16) == 15)
                        printf("\n");
                    else if (((i - 14) % 8) == 7)
                        printf("  ");
                }
                if ((pkt_data[12] = 0x08) && (pkt_data[13] == 0)) {
                    printf("    IP from %s to %s\n",
                           format_ip_addr((void*)&pkt_data[14+12]),
                           format_ip_addr((void*)&pkt_data[14+16]));
                }
            }
            continue;
        }
        pcb = pcap_deq(&pcap_free_queue);
        if (!pcb) {    // We are running behind, reuse one from the queue
            pcb = pcap_deq(&pcap_buffer_queue);
        }
        if (!pcb) {    // We have lost all our buffers
            printf("PCAP_READER RAN OUT OF MEMORY!!! (%d pkts)\n", pkts_rcvd);
            exit(-1);
        }
        memcpy(&pcb->ether_packet, (u_char*) pkt_data, pkt_header->len);
        pcb->size = pkt_header->len;
        pcap_enq(&pcap_buffer_queue, pcb);
        pkts_rcvd++;
    }
}
#endif // PCAP_THREADED

int init_adapter();

int num_of_ether_adapters;

int
get_adapters()
{
    find_adapters(true);
    return open_adapter(option_C);
}

#ifdef AF_LINK
// OpenBSD version uses getifaddrs()

int
find_adapters(boolean show)
{
    char                errbuf[PCAP_ERRBUF_SIZE];
    int                 num_of_ether_adapters;
    int                 adID;
    int                 i;
    char                devAddr[24];
    struct ifaddrs     *ifap;
    struct ifaddrs     *p;
    struct sockaddr_dl *sdp;
    unsigned char      *mp;

    // first get host name
    if (gethostname(localHostName,MAX_LOCAL_HOST_NAME))
        strcpy(localHostName,"Linux");

    for (i=0;i<strlen(localHostName);i++)
        if (localHostName[i]=='.')
           {localHostName[i]=0;break;}

    // ask pcap to find a valid device for use to sniff on
    //dev = pcap_lookupdev(errbuf);

    num_of_ether_adapters = 0;

    // getifaddrs() returns a list of addresses associated with our
    //    network interfaces. Each entry has the interface name,
    //    the address type of this entry and the data for this
    //    address. An example list may be:
    //        lo0: AF_LINK IFT_LOOP (0 bytes of address)
    //        lo0: AF_INET 127.0.0.1
    //        lo0: AF_INET6
    //        lo0: AF_INET6
    //        dc0: AF_LINK IFT_ETHER (6-byte MAC address)
    //        de0: AF_LINK IFT_ETHER (6-byte MAC address)
    //        de0: AF_INET 207.154.90.205
    //        de0: AF_INET6
    //        pflog0:  AF_LINK IFT_PFLOG  (0 bytes of address)
    //        pfsync0: AF_LINK IFT_PFSYNC (0 bytes of address)
    //        enc0:    AF_LINK IFT_ENC    (0 bytes of address)
    // The following code relies on the fact that we have never seen
    //     the IP address come before the MAC address, so we can
    //     skip all interfaces for which we have not seen a 6-byte
    //     MAC address.
    if (getifaddrs(&ifap) != 0)
    {
        printf("error %d in getifaddrs()\n", errno);
        exit(errno);
    }

    for (p = ifap; p; p = p->ifa_next)
    {
        if (debug_flags & DBFLG_ADAPTER)
            printf("%s: af=%d\n", p->ifa_name, p->ifa_addr->sa_family);

        switch (p->ifa_addr->sa_family)
        {
        case AF_LINK:
            sdp = (struct sockaddr_dl *)p->ifa_addr;
            mp = LLADDR(sdp);
            if (debug_flags & DBFLG_ADAPTER)
                printf("        AF_LINK type=%d alen=%d MAC=%s\n",
                       sdp->sdl_type, sdp->sdl_alen,
                       format_mac_addr(mp));
            if (sdp->sdl_alen != 6)
                continue;            // Not a usable MAC address format
            num_of_ether_adapters++;
            strncpy(Ethernet_adapters[num_of_ether_adapters].name,
                    p->ifa_name, IF_NAMESIZE);
            ethernet_adapters[num_of_ether_adapters].name[IF_NAMESIZE - 1] = 0;
            // mp = sdp->sdl_data + sdp->sdl_nlen;
            memcpy(ethernet_adapters[num_of_ether_adapters].mac_addr, mp, 6);
            break;
        case AF_INET:
            if (debug_flags & DBFLG_ADAPTER)
                printf("        AF_INET IP=%s\n",
                   format_ip_addr(&p->ifa_addr->sa_data[2]);
            if (strncmp(p->ifa_name,
                        ethernet_adapters[num_of_ether_adapters].name,
                        IF_NAMESIZE-1))
                continue;           // Name did not match: Skip 
            memcpy(ethernet_adapters[num_of_ether_adapters].ip_addr,
                   &p->ifa_addr->sa_data[2], sizeof(ip_addr));
            break;
        case AF_INET6:
        default:
            continue;
        }
    }
    freeifaddrs(ifap);

    dev = malloc(24);

    if (num_of_ether_adapters < 1) {
        fprint(stderr, "No ethernet adapters found !!!\n");
        exit(EXIT_FAILURE);
    } else if (num_of_ether_adapters == 1) {
        adID = 1;
        strcpy(dev,ethernet_adapters[adID].name);
        memcpy(my_mac_address,ethernet_adapters[adID].mac_addr,6);
    } else {
      int tries = 0;
      while (tries < 3)
      {
        printf("More than one ethernet adapters were found.\n");
        for (i=1; i <= num_of_ether_adapters; i++)
        {
            printf("#%d - adapter's name:%8s - MAC:%s - IP %s\n",
                i, ethernet_adapters[i].name,
                format_mac_addr(ethernet_adapters[i].mac_addr),
                format_ip_addr(&ethernet_adapters[i].ip_addr));
        }

        if (option_C >= 0) {
            adID = option_C;
        } else {
            printf("Please select local ethernet adapter 1 - %d (0 to specify yours) ",
               num_of_ether_adapters);
            scanf("%d",&adID);
        }

        if ((adID<0) || (adID>num_of_ether_adapters))
        {
              printf("Adapter id should be between 1 and %d (0 to specify yours)\n",
                  num_of_ether_adapters);
              printf("Please try again\n");
              tries++;
        }
        else if (adID==0)
        {
            printf("Please specify adapter's name (example eth0,eth1):");
            scanf("%s",dev);
            printf("Please specify adapter's MAC (example 00:D0:33:00:24:44):");
            scanf("%s",devAddr);
            if (isValidMAC(devAddr,my_mac_address))
                break;
            else
                printf("Invalid MAC address (%s). Try again\n",devAddr);
            tries++;
        }
        else
        {
            strcpy(dev,ethernet_adapters[adID].name);
            memcpy(my_mac_address,ethernet_adapters[adID].mac_addr,6);
            break;
        }
      }
    }

    /* error checking */
    if (dev == NULL)
    {
        printf("%s\n",errbuf);
        exit(1);
    }

    init_adapter(dev);

    return adID; // since you always select the first available ethernet adapter
}
#else
#ifdef SIOCGIFHWADDR
// Linux version
int
find_adapters(boolean show)
{
    char                errbuf[PCAP_ERRBUF_SIZE];
    int                 i;
    struct if_nameindex *iflist;
    int                  sock;
    struct ifreq         ifr;
    struct sockaddr     *sap;

    // first get host name
    if (gethostname(localHostName,MAX_LOCAL_HOST_NAME))
        strcpy(localHostName,"Linux");

    for (i=0;i<strlen(localHostName);i++)
        if (localHostName[i]=='.')
           {localHostName[i]=0;break;}

    // ask pcap to find a valid device for use to sniff on
    //dev = pcap_lookupdev(errbuf);

    num_of_ether_adapters = 0;

    iflist = if_nameindex();
    sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0) {
        printf("Failed to open socket - errno %d\n", errno);
        exit(errno);
    }

    for (i = 0; iflist[i].if_index; i++)
    {
        strcpy(ifr.ifr_name, iflist[i].if_name);
        if (ioctl(sock, SIOCGIFHWADDR, &ifr))
        {
            printf("Failed SIOCGIFHWADDR - errno = %d\n", errno);
            exit(errno);
        }
        sap = &ifr.ifr_hwaddr;
        if (debug_flags & DBFLG_ADAPTER)
        printf("#%d - adapter's name:%8s - MAC: %s\n",
            iflist[i].if_index, iflist[i].if_name,
            format_mac_addr((uint8 *)sap->sa_data));
        if ((sap->sa_data[0] == 0)
         && (sap->sa_data[1] == 0)
         && (sap->sa_data[2] == 0))
            continue;
        num_of_ether_adapters++;
        strncpy(ethernet_adapters[num_of_ether_adapters].name,
                iflist[i].if_name, IF_NAMESIZE);
        ethernet_adapters[num_of_ether_adapters].name[IF_NAMESIZE - 1] = 0;
        memcpy(ethernet_adapters[num_of_ether_adapters].mac_addr,
               sap->sa_data, 6);
        ethernet_adapters[num_of_ether_adapters].flags |= EA_USABLE;
        if (ioctl(sock, SIOCGIFADDR, &ifr))
        {
            int error = errno;
            int zero  = 0;
            if (error == EADDRNOTAVAIL) {
                memcpy(ethernet_adapters[num_of_ether_adapters].ip_addr,
                       &zero, sizeof(int));
                continue;
            } else {
                printf("Failed SIOCGIFADDR for %s - errno = %d\n",
                       ethernet_adapters[num_of_ether_adapters].name,
                       errno);
                perror("SIOCGIFADDR returns");
                exit(errno);
            }
        }
        memcpy(ethernet_adapters[num_of_ether_adapters].ip_addr,
               &sap->sa_data[2], sizeof(ip_addr));
    }
    if_freenameindex(iflist);
    close(sock);

    dev = malloc(24);

    if (num_of_ether_adapters <= 1)
        dev = pcap_lookupdev(errbuf); // use the system default
    else
    {
        printf("More than one ethernet adapters were found.\n");
        for (i=1; i <= num_of_ether_adapters; i++)
        {
            printf("#%d - adapter's name:%8s - MAC: %s - IP %s\n",
                i, ethernet_adapters[i].name,
                format_mac_addr(ethernet_adapters[i].mac_addr),
                format_ip_addr(&ethernet_adapters[i].ip_addr));
        }
    }
    return num_of_ether_adapters;
}
#else
#error You are hosed
#endif
#endif
int
open_adapter(int index)
{
    char adap[10];
    char errbuf[PCAP_ERRBUF_SIZE];
    int  promisc = 0; // false
    // timeout in millisecs ,if -1 it will block until new data shows up
    int  to_ms = 1;
    char *dev;

    if (num_of_ether_adapters == 1)
        index = 1;

    if ((index < 0) || (index > num_of_ether_adapters))
    {
    printf("Please select local ethernet adapter 1 - %d: ",
               num_of_ether_adapters);
    fgets(adap, 10, stdin);
    index = atoi(adap);
    }
    dev = ethernet_adapters[index].name;
    memcpy(my_mac_address, ethernet_adapters[index].mac_addr, 6);

    if (debug_flags & DBFLG_ADAPTER) printf("Opening =%s=\n", dev);
    descr = pcap_open_live(dev, PCAP_BUFSIZE, promisc,to_ms,errbuf);
    if(descr == NULL)
    {
        printf("pcap_open_live(%s): %s\n", dev, errbuf);
        exit(-1);
    }

    lh = libnet_init(LIBNET_LINK, dev, errbuf);
    if (!lh)
    {
        fprintf(stderr, "libnet_init: %s\n", errbuf);
        exit(-1);
    }
#ifdef PCAP_THREADED
    econ_phase = ECON_STARTING;
    pthread_create(&pcap_proc, NULL, (void *)pcap_reader, (void *)descr);
#endif
    return index;
}

int
close_all_adapters()
{
    econ_phase = ECON_CLOSE_ADAPTER;
    if (descr != NULL)
        pcap_close(descr);
#ifdef PCAP_THREADED
    pthread_join(pcap_proc, NULL);
#endif
    return 0; //everything OK
}

#ifdef PCAP_THREADED
int
adapter_receive(u_char *packet)
{
    int          size;
    pcap_buffer *pcb;

    pcb = pcap_deq(&pcap_buffer_queue);
    if (!pcb)
        return 0;

    size = pcb->size;
    memcpy(packet, pcb->ether_packet, size);
    pcap_enq(&pcap_free_queue, pcb);

    return size;
}
#else
int adapter_receive(u_char *packet)
{
    u_char *ptr;
    struct pcap_pkthdr hdr;     /* pcap.h */

    ptr = (u_char *)pcap_next(descr, &hdr);
    if (!ptr)
        return 0;

    memcpy(packet, (u_char*) ptr, hdr.len);
    return hdr.len;
}
#endif

int adapter_send(u_char* packet_to_send,int packet_size)
{
    int            n;
    libnet_ptag_t  ebuf_ptag = 0;
    epkt_header   *epacket = (epkt_header *)packet_to_send;

    ebuf_ptag = libnet_build_802_3(epacket->epkt_dest, epacket->epkt_src,
                                   packet_size, (uint8 *)epacket->epkt_payload,
                                   packet_size - 14, lh, ebuf_ptag);

    if (ebuf_ptag == -1)
    {
        fprintf(stderr, "libnet_build_802_3: %s\n",
                libnet_geterror(lh));
        exit(EXIT_FAILURE);
    }
    n = libnet_write(lh);
    if (n == -1)
    {
        int lb_en = errno;
        fprintf(stderr, "\nlibnet_write: %s\n  errno=%d  pk_size=%d\n",
                libnet_geterror(lh), lb_en, packet_size);
        exit(EXIT_FAILURE);
    }
    libnet_clear_packet(lh);
    ebuf_ptag = 0;
    return (n);
}
