#ifdef UNIX
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <errno.h>
#include <fcntl.h>
#ifdef SOLARIS
#include <sys/sockio.h> // for SIOCGIFADDR ioctl
#endif  // SOLARIS
#ifdef HAVE_IPV6
#ifdef MACOSX
#include <nameser.h>
#endif // MACOSX
#include <resolv.h>
#endif  // HAVE_IPV6
#endif  // UNIX

#ifdef WIN32
#include <winsock2.h>
#include <WS2tcpip.h>  // for extra socket options
#endif  // WIN32

// NOTE: This value should be confirmed.  It's the
//       apparently Linux-only approach we currently
//       use to set the IPv6 flow info header fields
//       Other approaches are under investigation
//       (We seem to be only able to affect
//        the "traffic class" bits at this time)
#ifndef IPV6_FLOWINFO_SEND
#define IPV6_FLOWINFO_SEND 33 // from linux/in6.h
#endif // SOL_IPV6

#include <stdio.h>
#include <string.h>

#include "debug.h"
#include "udpSocket.h"

// Hack for using with NRL IPSEC implementation
#ifdef NETSEC
#include <net/security.h>
extern void *netsec_request;
extern int netsec_requestlen;
#endif // NETSEC

// Use this macro (-DSOCKLEN_T=xxx) in your
// system's Makefile if the type socklen_t is 
// not defined for your system.
// (use "man getsockname" to see what type 
// is required for the last argument)

#ifdef SOCKLEN_T
#define socklen_t SOCKLEN_T
#else
#ifdef WIN32
#define socklen_t int
#endif // WIN32
#endif // if/else SOCKLEN_T

UdpSocket::UdpSocket()
    : handle(INVALID_SOCKET),
      addr_type(IPv4),
      port(0), connected(false), 
#ifdef HAVE_IPv6
      flow_label(0),
#endif // HAVE_IPV6
      owner(NULL), recv_handler(NULL), 
      install_func(NULL), install_data(NULL),
      user_data(NULL)
{
    
}

UdpSocket::~UdpSocket()
{
    Close();
}




// WIN32 needs the address type determine IPv6 _or_ IPv4 socket domain
// Note: WIN32 can't do IPv4 on an IPV6 socket!
UdpSocketError UdpSocket::Open(unsigned short       thePort, 
                               NetworkAddressType   type,
                               bool                 bindOnOpen)
{
    if (IsOpen())
    {
        DMSG(0, "UdpSocket::Open() already open!\n");
        return UDP_SOCKET_ERROR_OPEN;
    }
    int domain;
#ifdef HAVE_IPV6
    if(type == IPv6)
    {
        if (!IsIPv6Capable())
        {
            if (!SetIPv6Capable())
            {
                DMSG(0, "UdpSocket::Open() system not IPv6 capable?\n"); 
                return UDP_SOCKET_ERROR_OPEN;  
            }   
        }
        domain = PF_INET6;
        addr_type = IPv6;
    }
    else
#endif // HAVE_IPV6
    {
        domain = PF_INET;
        addr_type = IPv4;
    }    
    
#ifdef WIN32
    int family = (IPv6 == type) ? AF_INET6: AF_INET;
    // Startup WinSock
    WSADATA wsaData;
    WORD wVersionRequested = MAKEWORD( 2, 2 );
    int err = WSAStartup( wVersionRequested, &wsaData );
    if ( err != 0 ) return UDP_SOCKET_ERROR_OPEN;
 
    // Since we're doing QOS, we need find a QoS-capable UDP service provider
    WSAPROTOCOL_INFO* infoPtr = NULL;
    WSAPROTOCOL_INFO* protocolInfo = NULL;
    DWORD buflen = 0;
    // Query to properly size protocolInfo buffer
    WSAEnumProtocols(NULL, protocolInfo, &buflen);
    if (buflen)
    {
        // Enumerate, try to find QOS-capable UDP, and create a socket
        if ((protocolInfo = (WSAPROTOCOL_INFO*) new char[buflen]))
        {
            int count = WSAEnumProtocols(NULL, protocolInfo, &buflen);
            if (SOCKET_ERROR != count)
            {
                for (int i = 0; i < count; i++)
                {
                    if ((IPPROTO_UDP == protocolInfo[i].iProtocol) &&
                        (XP1_QOS_SUPPORTED & protocolInfo[i].dwServiceFlags1))
                    {
                        infoPtr = protocolInfo + i;
                    }
                }
            }
            else
            {
                DMSG(0, "UdpSocket: WSAEnumProtocols() error2!\n");
                return UDP_SOCKET_ERROR_OPEN;
            }
        }
        else
        {
            DMSG(0, "UdpSocket: Error allocating memory!\n");
            return UDP_SOCKET_ERROR_OPEN;
        }        
    }
    else
    {
        DMSG(0, "UdpSocket::Open() WSAEnumProtocols() error1!\n");            
    }

    // Use WSASocket() to open right kind of socket
    // (Just a regular socket if infoPtr == NULL
    handle = WSASocket(family, SOCK_DGRAM, 0,
                       infoPtr, 0, 
                       WSA_FLAG_OVERLAPPED | 
                       WSA_FLAG_MULTIPOINT_C_LEAF);
    if (protocolInfo) delete[] protocolInfo;
    if (INVALID_SOCKET == handle)
    {
        DMSG(0, "UdpSocket: WSASocket() error %d\n", WSAGetLastError());
        return UDP_SOCKET_ERROR_OPEN;
    }
#else
    if (INVALID_SOCKET == (handle = socket(domain, SOCK_DGRAM, 0)))
    {
       perror("UdpSocket: socket() error");
       return UDP_SOCKET_ERROR_OPEN;
    }
#endif // if/else WIN32
    
#ifdef NETSEC
    if (net_security_setrequest(handle, 0, netsec_request, netsec_requestlen))
    {
        perror("UdpSocket: net_security_setrequest() error");
        close(handle);
        handle = INVALID_SOCKET;
        return UDP_SOCKET_ERROR_OPEN;
    }
#endif // NETSEC

#ifdef UNIX
    if(-1 == fcntl(handle, F_SETFD, FD_CLOEXEC))
        perror("UdpSocket::Open() fcntl(FD_CLOEXEC) error");
    // Make the socket non-blocking
    if(-1 == fcntl(handle, F_SETFL, fcntl(handle, F_GETFL, 0) | O_NONBLOCK))
        perror("UdpSocket::Open() fcntl(F_SETFL(O_NONBLOCK)) error");
#endif // UNIX

    if (bindOnOpen)
    {
        UdpSocketError error = Bind(thePort);
        if (UDP_SOCKET_ERROR_NONE != error) return error;
    }
    else
    {
        port = 0;   
    }  // end if/else(bindOnOpen)

    return UDP_SOCKET_ERROR_NONE;  // no error
    
}  // end Open()

UdpSocketError UdpSocket::Bind(unsigned short thePort)
{
#ifdef HAVE_IPV6
    struct sockaddr_storage socketAddr;
    socklen_t addrSize;
    if (IPv6 == addr_type)
    {
        addrSize = sizeof(struct sockaddr_in6);
        memset((char*)&socketAddr, 0, sizeof(struct sockaddr_in6));    
        ((struct sockaddr_in6*)&socketAddr)->sin6_family = AF_INET6;
        ((struct sockaddr_in6*)&socketAddr)->sin6_addr = in6addr_any;
        ((struct sockaddr_in6*)&socketAddr)->sin6_port = htons(thePort);
        ((struct sockaddr_in6*)&socketAddr)->sin6_flowinfo = 0;
    }
    else
    {
        addrSize = sizeof(struct sockaddr_in);
        memset((char*)&socketAddr, 0, sizeof(struct sockaddr_in));    
        ((struct sockaddr_in*)&socketAddr)->sin_family = AF_INET;
        ((struct sockaddr_in*)&socketAddr)->sin_addr.s_addr = htonl(INADDR_ANY);
        ((struct sockaddr_in*)&socketAddr)->sin_port = htons(thePort);
    }
#else
    struct sockaddr socketAddr;
    socklen_t addrSize = sizeof(struct sockaddr_in);
    memset((char*)&socketAddr, 0, sizeof(struct sockaddr_in));    
    ((struct sockaddr_in*)&socketAddr)->sin_family = AF_INET;
    ((struct sockaddr_in*)&socketAddr)->sin_addr.s_addr = htonl(INADDR_ANY);
    ((struct sockaddr_in*)&socketAddr)->sin_port = htons(thePort);
#endif  //  if/else HAVE_IPV6
    // Bind the socket to the given port     
    if (bind(handle, (struct sockaddr*)&socketAddr, addrSize) < 0)
    {
       perror("UdpSocket: bind() error");
#ifdef UNIX
       close(handle);
#endif // UNIX
#ifdef WIN32
       closesocket(handle);
#endif // WIN32
       return UDP_SOCKET_ERROR_BIND;
    }

    // Get socket name so we know our port number  
    socklen_t sockLen = addrSize;
    if (getsockname(handle, (struct sockaddr*)&socketAddr, &sockLen) < 0) 
    {    
        DMSG(0, "UdpSocket: getsockname() error: %s\n", strerror(errno));
#ifdef UNIX
       close(handle);
#endif // UNIX
#ifdef WIN32
       closesocket(handle);
#endif // WIN32
        return UDP_SOCKET_ERROR_NAME;
    }

    switch(((struct sockaddr*)&socketAddr)->sa_family)
    {
        case AF_INET:    
            port = ntohs(((struct sockaddr_in*)&socketAddr)->sin_port);
            break;
#ifdef HAVE_IPV6        
        case AF_INET6:
            port = ntohs(((struct sockaddr_in6*)&socketAddr)->sin6_port);
            break;
#endif // HAVE_IPV6        
        default:
            perror("UdpSocket: getsockname() returned unknown address type");
#ifdef UNIX
            close(handle);
#endif // UNIX
#ifdef WIN32
            closesocket(handle);
#endif // WIN32
            return UDP_SOCKET_ERROR_NAME;;
     }
    if (install_func)
    {
        install_func(UDP_SOCKET_INSTALL, this, install_data);
    }

    return UDP_SOCKET_ERROR_NONE;
}  // end UdpSocket::Bind()

void UdpSocket::Close()
{
    if (IsOpen()) 
    {
        if (connected) Disconnect();
        if (install_func) install_func(UDP_SOCKET_REMOVE, this, install_data);
#ifdef UNIX
        close(handle);
#endif // UNIX
#ifdef WIN32
        closesocket(handle);
        WSACleanup();
#endif // WIN32
    }
    handle = INVALID_SOCKET;
    port = 0;
}  // end Close() 


bool UdpSocket::Connect(const NetworkAddress* theAddress)
{
#ifdef WIN32
    int result = WSAConnect(handle,
                            (struct sockaddr*)&theAddress->addr,
                             sizeof(struct sockaddr),
                             NULL, NULL, NULL, NULL);
    if (SOCKET_ERROR == result)
    {
      DMSG(0, "UdpSocket::Connect() WSAConnect() error: (%d)\n",
             WSAGetLastError());
      Disconnect();
      return false;
    }
    else
    {
        connected = true;
        return true;
    }
#else
    return false;  // (TBD) implement Connect() for UNIX
#endif // if/else WIN32
}  // end bool UdpSocket::Connect()

void UdpSocket::Disconnect()
{
#ifdef WIN32
    struct sockaddr nullAddr;
    memset(&nullAddr, 0 , sizeof(struct sockaddr));
    int result = WSAConnect(handle,
                            &nullAddr,
                            sizeof(struct sockaddr),
                            NULL, NULL, NULL, NULL);
    if (SOCKET_ERROR == result)
    {
      DMSG(0, "UdpSocket::Disconnect() WSAConnect() error: (%d)\n",
             WSAGetLastError());
    }
#endif  // WIN32
    connected = false;
}  // end UdpSocket::Disconnect()

UdpSocketError UdpSocket::SendTo(const NetworkAddress* destAddr, 
                                 const char* buffer, 
                                 unsigned int len)
{
    if (connected)
    {
        int result = send(handle, buffer, (size_t)len, 0);
        if(result < 0)
        {
            perror("UdpSocket::SendTo(): send() error:");
#ifdef WIN32
            DMSG(0, "UdpSocket::SendTo() error (%d)\n", WSAGetLastError());
#endif // WIN32
            return UDP_SOCKET_ERROR_SEND;
        }
        else
        {
            return UDP_SOCKET_ERROR_NONE;
        }
    }
    else
    {
        socklen_t addrSize;
#ifdef HAVE_IPV6
        if (flow_label && (IPv6 == destAddr->Type()))
            ((struct sockaddr_in6 *)(&destAddr->addr))->sin6_flowinfo = flow_label;
        if (IPv6 == destAddr->Type())
            addrSize = sizeof(struct sockaddr_in6);
        else
#endif //HAVE_IPV6
            addrSize = sizeof(struct sockaddr_in);
        int result = sendto(handle, buffer, (size_t)len, 0, (struct sockaddr*)&destAddr->addr, addrSize);
        if (result < 0)
        {
#ifdef WIN32
            DMSG(0, "UdpSocket::SendTo() error (%d)\n", WSAGetLastError());
#else
            DMSG(0, "UdpSocket::SendTo(): sendto() error: %s\n", strerror(errno));
#endif // WIN32
            return UDP_SOCKET_ERROR_SEND;
        }
        else
        {   
            return UDP_SOCKET_ERROR_NONE;
        }
    }
}  // end UdpSocket::SendTo()


UdpSocketError UdpSocket::RecvFrom(char*            buffer, 
                                   unsigned int*    buflen, 
                                   NetworkAddress*  sourceAddr)
{
    sourceAddr->len = sizeof(sourceAddr->addr);
    int result = recvfrom(handle, buffer, (size_t)*buflen, 0, 
                          (struct sockaddr*)&sourceAddr->addr, 
                          (socklen_t*)&sourceAddr->len);
    
    if (result < 0)
    {
#ifdef UNIX
        if (errno != EINTR)
            perror("UdpSocket: recvfrom() error");
#endif // UNIX
        *buflen = 0;
        return UDP_SOCKET_ERROR_RECV;
    }
    else
    {
        *buflen = result;
        switch(((struct sockaddr *)&sourceAddr->addr)->sa_family)
        {
            case AF_INET:
                sourceAddr->type = IPv4;
                    sourceAddr->len = 4;  // 4 bytes of IPv4 address
                    break;
        
            case AF_INET6:
                sourceAddr->type = IPv6;
                sourceAddr->len = 16;  // 16 bytes of IPv6 address
                break;
                
            default:
                DMSG(0, "UdpSocket::RecvFrom() Unsupported address type!\n");
                return UDP_SOCKET_ERROR_RECV;
        }
        return UDP_SOCKET_ERROR_NONE;
    }
}  // end UdpSocket::RecvFrom()

#ifdef HAVE_IPV6
#ifndef IPV6_ADD_MEMBERSHIP
#define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP
#define IPV6_DROP_MEMBERSHIP IPV6_LEAVE_GROUP
#endif // !IPV6_ADD_MEMBERSHIP
#endif // HAVE_IPV6 


UdpSocketError UdpSocket::JoinGroup(const NetworkAddress* groupAddress, 
                                    const char*           interfaceName)
{
#ifdef WIN32
    // (TBD) Add support for join on a specific interface for WIN32 ???
    SOCKET result = WSAJoinLeaf(handle, (struct sockaddr*)&groupAddress->addr,
                                sizeof(struct sockaddr), NULL, NULL, NULL, NULL,
                                JL_BOTH);
    if (INVALID_SOCKET == result)
    {
        DMSG(0, "WSAJoinLeaf() error: %d\n", WSAGetLastError());
        return UDP_SOCKET_ERROR_MCAST;
    }
#else
    int result;
#ifdef HAVE_IPV6
    if  (IPv6 == groupAddress->Type())
    {
        if (IN6_IS_ADDR_V4MAPPED(&((struct sockaddr_in6*)&groupAddress->addr)->sin6_addr))
        {
            struct ip_mreq mreq;
            mreq.imr_multiaddr.s_addr = 
                IN6_V4MAPPED_ADDR(&(((struct sockaddr_in6*)&groupAddress->addr)->sin6_addr));
            if (interfaceName)
            {
                NetworkAddress interfaceAddress;
                if (GetInterfaceAddress(interfaceName, IPv4, interfaceAddress))
                {
                    mreq.imr_interface.s_addr = htonl(interfaceAddress.IPv4HostAddr());
                }
                else
                {
                    DMSG(0, "UdpSocket::JoinGroup() invalid interface name\n");
                    return UDP_SOCKET_ERROR_MCAST;
                }
            }
            else
            {
                mreq.imr_interface.s_addr = INADDR_ANY;  
            } 
            result = setsockopt(handle, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&mreq, sizeof(mreq));
        }
        else
        {
            struct ipv6_mreq mreq;
            mreq.ipv6mr_multiaddr = ((struct sockaddr_in6*)&groupAddress->addr)->sin6_addr;
            if (interfaceName)
                mreq.ipv6mr_interface = GetInterfaceIndex(interfaceName);               
            else
                mreq.ipv6mr_interface = 0;
            result = setsockopt(handle, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, (char*)&mreq, sizeof(mreq));
        }
    }
    else
#endif // HAVE_IPV6
    {
        struct ip_mreq mreq;
        mreq.imr_multiaddr = ((struct sockaddr_in*)&groupAddress->addr)->sin_addr;
        if (interfaceName)
        {
            NetworkAddress interfaceAddress;
            if (GetInterfaceAddress(interfaceName, IPv4, interfaceAddress))
            {
                mreq.imr_interface.s_addr = htonl(interfaceAddress.IPv4HostAddr());
            }
            else
            {
                DMSG(0, "UdpSocket::JoinGroup() invalid interface name\n");
                return UDP_SOCKET_ERROR_MCAST;
            }
        }
        else
        {
            mreq.imr_interface.s_addr = INADDR_ANY;  
        }
        result = setsockopt(handle, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&mreq, sizeof(mreq));
    }
    if (result < 0)
    { 
        perror("UdpSocket: Error joining multicast group");
        return UDP_SOCKET_ERROR_MCAST;
    }    
#endif // if/else WIN32
    return UDP_SOCKET_ERROR_NONE;
}  // end UdpSocket::JoinGroup() 

#ifdef UNIX
// Helper functions for group joins & leaves
bool UdpSocket::GetInterfaceAddress(const char*         interfaceName,
                                    NetworkAddressType  addressType,
                                    NetworkAddress&     theAddress)
{
    struct ifreq req;
    strcpy(req.ifr_name, interfaceName);
    memset(&req.ifr_addr, 0, sizeof(struct sockaddr));
    int socketFd = -1;
    switch (addressType)
    {
        case IPv4:
            req.ifr_addr.sa_family = AF_INET;
            socketFd = socket(PF_INET, SOCK_DGRAM, 0);
            break;
#ifdef HAVE_IPV6
        case IPv6:
            req.ifr_addr.sa_family = AF_INET6;
            socketFd = socket(PF_INET6, SOCK_DGRAM, 0);
            break;
#endif // HAVE_IPV6
        default:
            req.ifr_addr.sa_family = AF_UNSPEC;
            socketFd = socket(PF_INET, SOCK_DGRAM, 0);
            break;
    }
    
    if (socketFd < 0)
    {
        DMSG(0, "LinuxRouteMgr::GetInterfaceAddress() socket() error: %s\n",
                strerror(errno)); 
        return false;
    }    
    
    if (ioctl(socketFd, SIOCGIFADDR, &req) < 0)
    {
        DMSG(0, "LinuxRouteMgr::GetInterfaceAddress() ioctl(SIOCGIFADDR) error: %s\n",
                strerror(errno)); 
        close(socketFd);
        return false;  
    }
    if (theAddress.SetAddress(&req.ifr_addr))
    {
        close(socketFd);
        return true;
    }
    else
    {
        DMSG(0, "LinuxRouteMgr::GetInterfaceAddress() invalid address family\n");
        close(socketFd);
        return false;
    }
}  // end UdpSocket::GetInterfaceAddress()

#ifdef HAVE_IPV6
unsigned int UdpSocket::GetInterfaceIndex(const char* interfaceName)
{
    struct if_nameindex* ifdx = if_nameindex();
    unsigned int index = 0;
    struct if_nameindex* ptr = ifdx;
    do
    {
        if (!strcmp(ptr->if_name, interfaceName))
        {
            index = ptr->if_index;
            break;  
        }
        ptr++;
    } while (0 != ptr->if_index);
    if_freenameindex(ifdx);
    return index;
}  // end UdpSocket::GetInterfaceIndex()

bool UdpSocket::GetInterfaceName(unsigned int index, char* buffer, unsigned int buflen)
{
    bool result = false;
    struct if_nameindex* ifdx = if_nameindex();
    struct if_nameindex* ptr = ifdx;
    do
    {
        if (index == ptr->if_index)
        {
            strncpy(buffer, ptr->if_name, buflen);
            result = true;
            break;
        }
        ptr++;
    } while (0 != ptr->if_index);
    if_freenameindex(ifdx);
    return result;
}  // end UdpSocket::GetInterfaceName()

#endif // HAVE_IPV6

#endif  // UNIX


UdpSocketError UdpSocket::LeaveGroup(const NetworkAddress* groupAddress,
                                     const char*           interfaceName)
{    
    int result;
#ifdef HAVE_IPV6
    if (IPv6 == groupAddress->Type())
    {
        if (IN6_IS_ADDR_V4MAPPED(&((struct sockaddr_in6*)&groupAddress->addr)->sin6_addr))
        {
            struct ip_mreq mreq;
            mreq.imr_multiaddr.s_addr = 
            IN6_V4MAPPED_ADDR(&(((struct sockaddr_in6*)&groupAddress->addr)->sin6_addr));
#ifdef UNIX
            if (interfaceName)
            {
                NetworkAddress interfaceAddress;
                if (GetInterfaceAddress(interfaceName, IPv4, interfaceAddress))
                {
                    mreq.imr_interface.s_addr = htonl(interfaceAddress.IPv4HostAddr());
                }
                else
                {
                    DMSG(0, "UdpSocket::LeaveGroup() invalid interface name\n");
                    return UDP_SOCKET_ERROR_MCAST;
                }
            }
            else
#endif // UNIX
                mreq.imr_interface.s_addr = INADDR_ANY; 
            result = setsockopt(handle, IPPROTO_IP, IP_DROP_MEMBERSHIP, (char*)&mreq, sizeof(mreq));
        }
        else
        {
            struct ipv6_mreq mreq;
            mreq.ipv6mr_multiaddr = ((struct sockaddr_in6*)&groupAddress->addr)->sin6_addr;
#ifdef UNIX
            if (interfaceName)
                mreq.ipv6mr_interface = GetInterfaceIndex(interfaceName);
            else
#endif // UNIX
                mreq.ipv6mr_interface = 0;
            result = setsockopt(handle, IPPROTO_IPV6, IPV6_DROP_MEMBERSHIP, (char*)&mreq, sizeof(mreq));
        }
    }
    else
#endif
    {
        struct ip_mreq mreq;
        mreq.imr_multiaddr = ((struct sockaddr_in*)&groupAddress->addr)->sin_addr;
#ifdef UNIX
        if (interfaceName)
        {
            NetworkAddress interfaceAddress;
            if (GetInterfaceAddress(interfaceName, IPv4, interfaceAddress))
            {
                mreq.imr_interface.s_addr = htonl(interfaceAddress.IPv4HostAddr());
            }
            else
            {
                DMSG(0, "UdpSocket::JoinGroup() invalid interface name\n");
                return UDP_SOCKET_ERROR_MCAST;
            }
        }
        else
#endif // UNIX
                mreq.imr_interface.s_addr = INADDR_ANY; 
        result = setsockopt(handle, IPPROTO_IP, IP_DROP_MEMBERSHIP, (char*)&mreq, sizeof(mreq));
    }
    if (result < 0)
    {
        perror("UdpSocket: Error leaving multicast group");
        return UDP_SOCKET_ERROR_MCAST;
    }
    else
    {
        return UDP_SOCKET_ERROR_NONE;
    }
}  // end UdpSocket::LeaveGroup() 

UdpSocketError UdpSocket::SetTTL(unsigned char ttl)
{

#ifdef WIN32
    DWORD dwTTL = (DWORD)ttl; 
    DWORD dwBytesXfer;
    if (WSAIoctl(handle, SIO_MULTICAST_SCOPE, &dwTTL, sizeof(dwTTL),
                 NULL, 0, &dwBytesXfer,    NULL, NULL))
#else
    int result;
#ifdef HAVE_IPV6
    if (IPv6 == addr_type)
    {
        unsigned int hops = (unsigned int) ttl;
        result = setsockopt(handle, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, 
                            &hops, sizeof(hops));
    }
    else
#endif // HAVE_IPV6
    {
        result = setsockopt(handle, IPPROTO_IP, IP_MULTICAST_TTL, 
                            (char*)&ttl, sizeof(ttl));
    }
    if (result < 0) 
#endif // if/else WIN32
    { 
        perror("UdpSocket: setsockopt(IP_MULTICAST_TTL) error"); 
        return UDP_SOCKET_ERROR_MCAST;  /* Evidently non-mcast kernel */
    }
    else
    {   
        return UDP_SOCKET_ERROR_NONE;
    }
}  // end UdpSocket::SetTTL()

UdpSocketError UdpSocket::SetTOS(unsigned char tos)
{ 
   ASSERT(true == IsOpen());
#ifdef NEVER_EVER // (for older LINUX?)                  
   int precedence = IPTOS_PREC(tos);
   if (setsockopt(handle, SOL_SOCKET, SO_PRIORITY, (char*)&precedence, sizeof(precedence)) < 0)           
   {     
       perror("UdpSocket: setsockopt(SO_PRIORITY) error"); 
       return UDP_SOCKET_ERROR_TOS;
    }                          
   int tos_bits = IPTOS_TOS(tos);
   if (setsockopt(handle, SOL_IP, IP_TOS, (char*)&tos_bits, sizeof(tos_bits)) < 0) 
#else
    int theTOS = tos;
    int result;
#ifdef HAVE_IPV6
    if (IPv6 == addr_type)
        result = setsockopt(handle, IPPROTO_IPV6, IP_TOS, (char*)&theTOS, sizeof(theTOS)); 
    else 
#endif // HAVE_IPV6  
        result =  setsockopt(handle, IPPROTO_IP, IP_TOS, (char*)&theTOS, sizeof(theTOS));    
#endif  // if/else NEVER_EVER
    if (result < 0)
    {               
        perror("UdpSocket: setsockopt(IP_TOS) error");
        return UDP_SOCKET_ERROR_TOS;
    }
    return UDP_SOCKET_ERROR_NONE; 
}  // end UdpSocket::SetTOS()

UdpSocketError UdpSocket::SetBroadcast(bool broadcast)
{
#ifdef SO_BROADCAST
#ifdef WIN32
    BOOL state = broadcast ? TRUE : FALSE;
#else
    int state = broadcast ? 1 : 0;
#endif // if/else WIN32
    if (setsockopt(handle, SOL_SOCKET, SO_BROADCAST, (char*)&state, sizeof(state)) < 0)
    {
        DMSG(0, "UdpSocket::SetBroadcast(): setsockopt(SO_BROADCAST) error: %s\n",
                strerror(errno));
        return UDP_SOCKET_ERROR_BCAST;
    }
#endif // SO_BROADCAST
    return UDP_SOCKET_ERROR_NONE;
}  // end UdpSocket::SetBroadcast()


UdpSocketError UdpSocket::SetLoopback(bool loopback)
{
    ASSERT(IsOpen());
#ifdef WIN32
    DWORD dwLoop = loopback ? TRUE: FALSE;
    DWORD dwBytesXfer;
    if (WSAIoctl(handle, SIO_MULTIPOINT_LOOPBACK , &dwLoop, sizeof(dwLoop),
             NULL, 0, &dwBytesXfer,    NULL, NULL))
#else 
    int result;
    char loop = loopback ? 1 : 0;
#ifdef HAVE_IPV6
    if (IPv6 == addr_type)
        result = setsockopt(handle, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, 
                            (char*)&loop, sizeof(loop));
    else
#endif // HAVE_IPV6
    result = setsockopt(handle, IPPROTO_IP, IP_MULTICAST_LOOP, 
                        (char*)&loop, sizeof(loop));
    if (result < 0)
#endif // if/else WIN32
    {
        perror("UdpSocket: setsockopt(IP_MULTICAST_LOOP) error"); // let this error slide
    } 
    return UDP_SOCKET_ERROR_NONE;
}  // end UdpSocket::SetLoopback() 

UdpSocketError UdpSocket::SetMulticastInterface(const char* interfaceName)
{    
    if (interfaceName)
    {
#ifdef WIN32
        // (TBD) fix this for WIN32
        return UDP_SOCKET_ERROR_IFACE;
#else        
        int result;
#ifdef HAVE_IPV6
        if (IPv6 == addr_type)  
        {
            unsigned int interfaceIndex = GetInterfaceIndex(interfaceName);
            result = setsockopt(handle, IPPROTO_IPV6, IPV6_MULTICAST_IF,
                                (char*)&interfaceIndex, sizeof(interfaceIndex));
        }
        else 
#endif // HAVE_IPV6   
        {  
            struct in_addr localAddr;
            NetworkAddress interfaceAddress;
            if (GetInterfaceAddress(interfaceName, IPv4, interfaceAddress))
            {
                localAddr.s_addr = htonl(interfaceAddress.IPv4HostAddr());
            }
            else
            {
                DMSG(0, "UdpSocket::SetMulticastInterface() invalid interface name\n");
                return UDP_SOCKET_ERROR_MCAST;
            }
            result = setsockopt(handle, IPPROTO_IP, IP_MULTICAST_IF, (char*)&localAddr, 
                                sizeof(localAddr));
        }
        if (result < 0)
        { 
            perror("UdpSocket: setsockopt(IP_MULTICAST_IF) error");
            return UDP_SOCKET_ERROR_IFACE;
        } 
#endif // if/else WIN32         
    }     
    return UDP_SOCKET_ERROR_NONE;
}  // end UdpSocket::SetMulticastInterface()

UdpSocketError UdpSocket::SetReuse(bool state)
{
#if defined(SO_REUSEADDR) || defined(SO_REUSEPORT)
    UdpSocketError err = UDP_SOCKET_ERROR_NONE;
#else
    return UDP_SOCKET_ERROR_REUSE;
#endif
    int reuse = state ? 1 : 0;
#ifdef SO_REUSEADDR
#ifdef WIN32
    BOOL breuse = (BOOL)reuse;
    if (setsockopt(handle, SOL_SOCKET, SO_REUSEADDR, (char*)&breuse, sizeof(breuse)) < 0)
#else
    if (setsockopt(handle, SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, sizeof(reuse)) < 0)
#endif // if/else WIN32
    {
        perror("UdpSocket: setsockopt(REUSE_ADDR)");
        err = UDP_SOCKET_ERROR_REUSE;
    }
#endif // SO_REUSEADDR            
            
#ifdef SO_REUSEPORT  // not defined on Linux for some reason?
#ifdef WIN32
    BOOL breuse = (BOOL)reuse;
    if (setsockopt(handle, SOL_SOCKET, SO_REUSEPORT, (char*)&breuse, sizeof(i_reuse)) < 0)
#else
    if (setsockopt(handle, SOL_SOCKET, SO_REUSEPORT, (char*)&reuse, sizeof(reuse)) < 0)
#endif // if/else WIN32
    {
        perror("UdpSocket: setsockopt(REUSE_ADDR)");
        err = UDP_SOCKET_ERROR_REUSE;
    }
#endif // SO_REUSEPORT
    return err;
}  // end UdpSocketError::SetReuse()

#ifdef HAVE_IPV6
bool UdpSocket::IsIPv6Capable()
{
#ifdef WIN32
    WSADATA wsaData;
    WORD wVersionRequested = MAKEWORD(2, 0);
    int err = WSAStartup( wVersionRequested, &wsaData );
    if ( err != 0 ) return false;
    SOCKET handle = socket(AF_INET6, SOCK_DGRAM, 0);
    closesocket(handle);
    WSACleanup();
    if(INVALID_SOCKET == handle)
        return false;
    else
        return true;
#else
#ifdef RES_USE_INET6
    if (0 == (_res.options & RES_INIT)) res_init();
    if (0 == (_res.options & RES_USE_INET6))
        return false;
    else
#endif // RES_USE_INET6
        return true;
#endif  // if/else WIN32
}

bool UdpSocket::SetIPv6Capable()
{
#ifdef WIN32
    WSADATA wsaData;
    WORD wVersionRequested = MAKEWORD(2, 0);
    int err = WSAStartup( wVersionRequested, &wsaData );
    if ( err != 0 ) return false;
    SOCKET handle = socket(AF_INET6, SOCK_DGRAM, 0);
    closesocket(handle);
    WSACleanup();
    if(INVALID_SOCKET == handle)
        return false;
    else
        return true;
#else
#ifdef RES_USE_INET6
    if (0 == (_res.options & RES_INIT)) 
        res_init();
    if (0 == (_res.options & RES_USE_INET6)) 
        _res.options |= RES_USE_INET6; 
    if (0 == (_res.options & RES_USE_INET6))
        return false;
    else
#endif // RES_USE_INET6
        return true;
#endif  // if/else WIN32
}  // end UdpSocket::SetIPv6Capable()
#endif // HAVE_IPV6


bool UdpSocket::SetTxBufferSize(unsigned int bufferSize)
{
   if (bufferSize <= 0) return false;
   if (!IsOpen())
   {
        DMSG(0, "UdpSocket::SetTxBufferSize() error: socket closed\n");     
        return false;
   }
   unsigned int oldBufferSize = GetTxBufferSize();
   if (setsockopt(handle, SOL_SOCKET, SO_SNDBUF, (char*)&bufferSize, sizeof(bufferSize)) < 0) 
   {
        setsockopt(handle, SOL_SOCKET, SO_SNDBUF, (char*)&oldBufferSize, sizeof(oldBufferSize));
        perror("UdpSocket::SetTxBufferSize() setsockopt(SO_SNDBUF) error setting buffer size");
        return false;
   }
   return true;
}  // end UdpSocket::SetTxBufferSize()

unsigned int UdpSocket::GetTxBufferSize()
{
    if (!IsOpen()) return 0;
    unsigned int txBufferSize = 0;
    socklen_t len = sizeof(txBufferSize);
    if (getsockopt(handle, SOL_SOCKET, SO_SNDBUF, (char*)&txBufferSize, &len) < 0) 
    {
        perror("UdpSocket: getsockopt(SO_SNDBUF) error");
	    return 0; 
    }
    return ((unsigned int)txBufferSize);
}  // end UdpSocket::GetTxBufferSize()

bool UdpSocket::SetRxBufferSize(unsigned int bufferSize)
{
   if (bufferSize <= 0) return false;
   if (!IsOpen())
   {
        DMSG(0, "UdpSocket::SetRxBufferSize() error: socket closed\n");    
        return false;
   }
   unsigned int oldBufferSize = GetTxBufferSize();
   if (setsockopt(handle, SOL_SOCKET, SO_RCVBUF, (char*)&bufferSize, sizeof(bufferSize)) < 0) 
   {
        setsockopt(handle, SOL_SOCKET, SO_RCVBUF, (char*)&oldBufferSize, sizeof(oldBufferSize));
        perror("UdpSocket::SetRxBufferSize() setsockopt(SO_RCVBUF) error setting buffer size");
        return false;
   }
   return true;
}  // end UdpSocket::SetRxBufferSize()

unsigned int UdpSocket::GetRxBufferSize()
{
    if (!IsOpen()) return 0;
    unsigned int rxBufferSize = 0;
    socklen_t len = sizeof(rxBufferSize);
    if (getsockopt(handle, SOL_SOCKET, SO_RCVBUF, (char*)&rxBufferSize, &len) < 0) 
    {
        perror("UdpSocket::GetRxBufferSize() getsockopt(SO_RCVBUF) error");
	    return 0; 
    }
    return ((unsigned int)rxBufferSize);
}  // end UdpSocket::GetRxBufferSize()

#ifdef HAVE_IPV6
bool UdpSocket::SetFlowLabel(UINT32 label)  
{
    int result = 0; 
#ifdef SOL_IPV6
    if (label && !flow_label)
    {
       int on = 1;
       result = setsockopt(handle, SOL_IPV6, IPV6_FLOWINFO_SEND, (void*)&on, sizeof(on));
    }
    else if (!label && flow_label)
    {
        int off = 0;
        result = setsockopt(handle, SOL_IPV6, IPV6_FLOWINFO_SEND, (void*)&off, sizeof(off));
    }
#endif  //SOL_IPV6
    if (0 == result)
    {
        flow_label = htonl(label);
        return true;
    }
    else
    {
        perror("UdpSocket::SetFlowLabel() setsockopt(SOL_IPV6) error");
        return false;
    }    
}  // end UdpSocket::SetFlowLabel()
#endif  //HAVE_IPV6
