/**
 **	File ......... UdpSocket.cpp
 **	Published ....  2004-02-13
 **	Author ....... grymse@alhem.net
**/
/*
Copyright (C) 2004,2005  Anders Hedstrom

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/
#ifdef _WIN32
#pragma warning(disable:4786)
#include <stdlib.h>
#else
#include <errno.h>
#endif
#include <stdio.h>
#include <map>

#include <StdLog.h>
#include "SocketHandler.h"
#include "UdpSocket.h"
// include this to see strange sights
//#include <linux/in6.h>

#ifdef _DEBUG
#define DEB(x) x
#else
#define DEB(x) 
#endif


UdpSocket::UdpSocket(SocketHandler& h,int ibufsz) : Socket(h)
,m_connected(false)
,m_ibuf(new char[ibufsz])
,m_ibufsz(ibufsz)
{
}


UdpSocket::~UdpSocket()
{
	delete[] m_ibuf;
}


SOCKET UdpSocket::Bind(port_t &port,int range)
{
	SOCKET s = GetSocket();
	if (s == INVALID_SOCKET)
	{
		s = CreateSocket4(SOCK_DGRAM, "udp");
		if (s == INVALID_SOCKET)
		{
			return s;
		}
		Attach(s);
	}
	struct sockaddr_in sa;
	socklen_t sa_len = sizeof(sa);
	ipaddr_t l = 0;

	memset(&sa,0,sizeof(sa));
	sa.sin_family = AF_INET;
	sa.sin_port = htons( port );
	memmove(&sa.sin_addr,&l,4);

	int n = bind(s, (struct sockaddr *)&sa, sa_len);
	int tries = range;
	while (n == -1 && tries--)
	{
		sa.sin_port = htons( ++port );
		n = bind(s, (struct sockaddr *)&sa, sa_len);
	}
	if (n == -1)
	{
		Handler().LogError(this, "bind", Errno, StrError(Errno), LOG_LEVEL_FATAL);
		SetCloseAndDelete();
		Close();
		return INVALID_SOCKET;
	}
	return s;
}


#ifdef IPPROTO_IPV6
SOCKET UdpSocket::Bind6(port_t &port,int range)
{
	SOCKET s = GetSocket();
	if (s == INVALID_SOCKET)
	{
		s = CreateSocket6(SOCK_DGRAM, "udp");
		if (s == INVALID_SOCKET)
		{
			return s;
		}
		Attach(s);
	}
	struct sockaddr_in6 sa;
	socklen_t sa_len = sizeof(sa);

	memset(&sa,0,sizeof(sa));
	sa.sin6_family = AF_INET6;
	sa.sin6_port = htons( port );
	sa.sin6_flowinfo = 0;
	sa.sin6_scope_id = 0;
	// sa.sin6_addr is all 0's

	int n = bind(s, (struct sockaddr *)&sa, sa_len);
	int tries = range;
	while (n == -1 && tries--)
	{
		sa.sin6_port = htons( ++port );
		n = bind(s, (struct sockaddr *)&sa, sa_len);
	}
	if (n == -1)
	{
		Handler().LogError(this, "bind", Errno, StrError(Errno), LOG_LEVEL_FATAL);
		SetCloseAndDelete();
		Close();
		return INVALID_SOCKET;
	}
	return s;
}
#endif


/** if you wish to use Send, first Open a connection */
bool UdpSocket::Open(ipaddr_t l,port_t port)
{
	if (GetSocket() == INVALID_SOCKET)
	{
		SOCKET s = CreateSocket4(SOCK_DGRAM, "udp");
		if (s == INVALID_SOCKET)
		{
			return false;
		}
		Attach(s);
	}
	struct sockaddr_in sa;
	socklen_t sa_len = sizeof(sa);

	memset(&sa,0,sizeof(sa));
	sa.sin_family = AF_INET;
	sa.sin_port = htons( port );
	memmove(&sa.sin_addr,&l,4);

	if (connect(GetSocket(), (struct sockaddr *)&sa, sa_len) == -1)
	{
		Handler().LogError(this, "connect", Errno, StrError(Errno), LOG_LEVEL_FATAL);
		SetCloseAndDelete();
		return false;
	}
	m_connected = true;
	return true;
}


bool UdpSocket::Open(const std::string& host,port_t port)
{
	if (GetSocket() == INVALID_SOCKET)
	{
		SOCKET s = CreateSocket4(SOCK_DGRAM, "udp");
		if (s == INVALID_SOCKET)
		{
			return false;
		}
		Attach(s);
	}
	ipaddr_t a;
	if (u2ip(host, a))
	{
		return Open(a, port);
	}
	return false;
}


#ifdef IPPROTO_IPV6
bool UdpSocket::Open6(struct in6_addr& a,port_t port)
{
	if (GetSocket() == INVALID_SOCKET)
	{
		SOCKET s = CreateSocket6(SOCK_DGRAM, "udp");
		if (s == INVALID_SOCKET)
		{
			return false;
		}
		Attach(s);
	}
	struct sockaddr_in6 sa;
	socklen_t sa_len = sizeof(sa);

	memset(&sa,0,sizeof(sa));
	sa.sin6_family = AF_INET6;
	sa.sin6_port = htons( port );
	sa.sin6_flowinfo = 0;
	sa.sin6_scope_id = 0;
	sa.sin6_addr = a;

	if (connect(GetSocket(), (struct sockaddr *)&sa, sa_len) == -1)
	{
		Handler().LogError(this, "connect", Errno, StrError(Errno), LOG_LEVEL_FATAL);
		SetCloseAndDelete();
		return false;
	}
	m_connected = true;
	return true;
}


bool UdpSocket::Open6(const std::string& host,port_t port)
{
	if (GetSocket() == INVALID_SOCKET)
	{
		SOCKET s = CreateSocket6(SOCK_DGRAM, "udp");
		if (s == INVALID_SOCKET)
		{
			return false;
		}
		Attach(s);
	}
	struct in6_addr a;
	if (u2ip(host, a))
	{
		return Open6(a, port);
	}
	return false;
}
#endif


void UdpSocket::CreateConnection()
{
	if (GetSocket() == INVALID_SOCKET)
	{
		SOCKET s = CreateSocket4(SOCK_DGRAM, "udp");
		if (s == INVALID_SOCKET)
		{
			return;
		}
		Attach(s);
	}
}


#ifdef IPPROTO_IPV6
void UdpSocket::CreateConnection6()
{
	if (GetSocket() == INVALID_SOCKET)
	{
		SOCKET s = CreateSocket6(SOCK_DGRAM, "udp");
		if (s == INVALID_SOCKET)
		{
			return;
		}
		Attach(s);
	}
}
#endif // IPPROTO_IPV6


/** send to specified address */
void UdpSocket::SendToBuf(const std::string& h,port_t p,const char *data,int len,int flags)
{
	if (GetSocket() == INVALID_SOCKET)
	{
		SOCKET s = CreateSocket4(SOCK_DGRAM, "udp");
		if (s == INVALID_SOCKET)
		{
			return;
		}
		Attach(s);
	}
	ipaddr_t a;
	if (u2ip(h,a))
	{
		struct sockaddr_in sa;
		socklen_t sa_len = sizeof(sa);

		memset(&sa,0,sizeof(sa));
		sa.sin_family = AF_INET;
		sa.sin_port = htons( p );
		memmove(&sa.sin_addr,&a,4);

		if (sendto(GetSocket(),data,len,flags,(struct sockaddr *)&sa,sa_len) == -1)
		{
			Handler().LogError(this,"sendto",Errno,StrError(Errno),LOG_LEVEL_ERROR);
		}
	}
}


/** send to specified address */
void UdpSocket::SendToBuf(ipaddr_t a,port_t p,const char *data,int len,int flags)
{
	if (GetSocket() == INVALID_SOCKET)
	{
		SOCKET s = CreateSocket4(SOCK_DGRAM, "udp");
		if (s == INVALID_SOCKET)
		{
			return;
		}
		Attach(s);
	}
	{
		struct sockaddr_in sa;
		socklen_t sa_len = sizeof(sa);

		memset(&sa,0,sizeof(sa));
		sa.sin_family = AF_INET;
		sa.sin_port = htons( p );
		memmove(&sa.sin_addr,&a,4);

		if (sendto(GetSocket(),data,len,flags,(struct sockaddr *)&sa,sa_len) == -1)
		{
			Handler().LogError(this,"sendto",Errno,StrError(Errno),LOG_LEVEL_ERROR);
		}
	}
}


#ifdef IPPROTO_IPV6
void UdpSocket::SendToBuf6(const std::string& h,port_t p,const char *data,int len,int flags)
{
	if (GetSocket() == INVALID_SOCKET)
	{
		SOCKET s = CreateSocket6(SOCK_DGRAM, "udp");
		if (s == INVALID_SOCKET)
		{
			return;
		}
		Attach(s);
	}
	struct in6_addr a;
	if (u2ip(h,a))
	{
		struct sockaddr_in6 sa;
		socklen_t sa_len = sizeof(sa);

		memset(&sa,0,sizeof(sa));
		sa.sin6_family = AF_INET6;
		sa.sin6_port = htons( p );
		sa.sin6_flowinfo = 0;
		sa.sin6_scope_id = 0;
		sa.sin6_addr = a;

		if (sendto(GetSocket(),data,len,flags,(struct sockaddr *)&sa,sa_len) == -1)
		{
			Handler().LogError(this,"sendto",Errno,StrError(Errno),LOG_LEVEL_ERROR);
		}
	}
}
#endif


void UdpSocket::SendTo(const std::string& a,port_t p,const std::string& str,int flags)
{
	SendToBuf(a,p,str.c_str(),(int)str.size(),flags);
}


void UdpSocket::SendTo(ipaddr_t a,port_t p,const std::string& str,int flags)
{
	SendToBuf(a,p,str.c_str(),(int)str.size(),flags);
}


#ifdef IPPROTO_IPV6
void UdpSocket::SendTo6(const std::string& a,port_t p,const std::string& str,int flags)
{
	SendToBuf6(a,p,str.c_str(),(int)str.size(),flags);
}
#endif


/** send to connected address */
void UdpSocket::SendBuf(const char *data,int len,int flags)
{
	if (!m_connected)
	{
		Handler().LogError(this,"SendBuf",0,"not connected",LOG_LEVEL_ERROR);
		return;
	}
	if (send(GetSocket(),data,len,flags) == -1)
	{
		Handler().LogError(this,"send",Errno,StrError(Errno),LOG_LEVEL_ERROR);
	}
}


void UdpSocket::Send(const std::string& str,int flags)
{
	SendBuf(str.c_str(),(int)str.size(),flags);
}


void UdpSocket::OnRead()
{
#ifdef IPPROTO_IPV6
	if (IsIpv6())
	{
		struct sockaddr_in6 sa;
		socklen_t sa_len = sizeof(sa);
		int n = recvfrom(GetSocket(), m_ibuf, m_ibufsz, 0, (struct sockaddr *)&sa, &sa_len);
		if (n == -1)
		{
			Handler().LogError(this, "recvfrom", Errno, StrError(Errno), LOG_LEVEL_ERROR);
			return;
		}
		if (sa_len != sizeof(sa))
		{
			Handler().LogError(this, "recvfrom", 0, "unexpected address struct size", LOG_LEVEL_WARNING);
		}
		this -> OnRawData(m_ibuf, n, (struct sockaddr *)&sa, sa_len);
		return;
	}
#endif
	struct sockaddr_in sa;
	socklen_t sa_len = sizeof(sa);
	int n = recvfrom(GetSocket(), m_ibuf, m_ibufsz, 0, (struct sockaddr *)&sa, &sa_len);
	if (n == -1)
	{
		Handler().LogError(this, "recvfrom", Errno, StrError(Errno), LOG_LEVEL_ERROR);
		return;
	}
	if (sa_len != sizeof(sa))
	{
		Handler().LogError(this, "recvfrom", 0, "unexpected address struct size", LOG_LEVEL_WARNING);
	}
	this -> OnRawData(m_ibuf, n, (struct sockaddr *)&sa, sa_len);
}


void UdpSocket::SetBroadcast(bool b)
{
	int one = 1;
	int zero = 0;

	if (b)
	{
		if (setsockopt(GetSocket(), SOL_SOCKET, SO_BROADCAST, (char *) &one, sizeof(one)) == -1)
		{
			Handler().LogError(this, "SetBroadcast", Errno, StrError(Errno), LOG_LEVEL_WARNING);
		}
	}
	else
	{
		if (setsockopt(GetSocket(), SOL_SOCKET, SO_BROADCAST, (char *) &zero, sizeof(zero)) == -1)
		{
			Handler().LogError(this, "SetBroadcast", Errno, StrError(Errno), LOG_LEVEL_WARNING);
		}
	}
}


bool UdpSocket::IsBroadcast()
{
	int is_broadcast = 0;
	socklen_t size;
	if (getsockopt(GetSocket(), SOL_SOCKET, SO_BROADCAST, (char *)&is_broadcast, &size) == -1)
	{
		Handler().LogError(this, "IsBroadcast", Errno, StrError(Errno), LOG_LEVEL_WARNING);
	}
	return is_broadcast != 0;
}


void UdpSocket::SetMulticastTTL(int ttl)
{
	if (setsockopt(GetSocket(), SOL_IP, IP_MULTICAST_TTL, (char *)&ttl, sizeof(int)) == -1)
	{
		Handler().LogError(this, "SetMulticastTTL", Errno, StrError(Errno), LOG_LEVEL_WARNING);
	}
}


int UdpSocket::GetMulticastTTL()
{
	int ttl = 0;
	socklen_t size = sizeof(int);
	if (getsockopt(GetSocket(), SOL_IP, IP_MULTICAST_TTL, (char *)&ttl, &size) == -1)
	{
		Handler().LogError(this, "GetMulticastTTL", Errno, StrError(Errno), LOG_LEVEL_WARNING);
	}
	return ttl;
}


void UdpSocket::SetMulticastLoop(bool x)
{
#ifdef IPPROTO_IPV6
	if (IsIpv6())
	{
		int val = x ? 1 : 0;
		if (setsockopt(GetSocket(), IPPROTO_IPV6, IPV6_MULTICAST_LOOP, (char *)&val, sizeof(int)) == -1)
		{
			Handler().LogError(this, "SetMulticastLoop", Errno, StrError(Errno), LOG_LEVEL_WARNING);
		}
	}
#endif
	int val = x ? 1 : 0;
	if (setsockopt(GetSocket(), SOL_IP, IP_MULTICAST_LOOP, (char *)&val, sizeof(int)) == -1)
	{
		Handler().LogError(this, "SetMulticastLoop", Errno, StrError(Errno), LOG_LEVEL_WARNING);
	}
}


bool UdpSocket::IsMulticastLoop()
{
#ifdef IPPROTO_IPV6
	if (IsIpv6())
	{
		int is_loop = 0;
		socklen_t size = sizeof(int);
		if (getsockopt(GetSocket(), IPPROTO_IPV6, IPV6_MULTICAST_LOOP, (char *)&is_loop, &size) == -1)
		{
			Handler().LogError(this, "IsMulticastLoop", Errno, StrError(Errno), LOG_LEVEL_WARNING);
		}
		return is_loop ? true : false;
	}
#endif
	int is_loop = 0;
	socklen_t size = sizeof(int);
	if (getsockopt(GetSocket(), SOL_IP, IP_MULTICAST_LOOP, (char *)&is_loop, &size) == -1)
	{
		Handler().LogError(this, "IsMulticastLoop", Errno, StrError(Errno), LOG_LEVEL_WARNING);
	}
	return is_loop ? true : false;
}


void UdpSocket::AddMulticastMembership(const std::string& group,const std::string& local_if,int if_index)
{
#ifdef IPPROTO_IPV6
	if (IsIpv6())
	{
		struct ipv6_mreq x;
		struct in6_addr addr;
		if (u2ip( group, addr ))
		{
			x.ipv6mr_multiaddr = addr;
			x.ipv6mr_interface = if_index;
			if (setsockopt(GetSocket(), IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, (char *)&x, sizeof(struct ipv6_mreq)) == -1)
			{
				Handler().LogError(this, "AddMulticastMembership", Errno, StrError(Errno), LOG_LEVEL_WARNING);
			}
		}
		return;
	}
#endif
	struct ip_mreq x; // ip_mreqn
	ipaddr_t addr;
	if (u2ip( group, addr ))
	{
		memcpy(&x.imr_multiaddr.s_addr, &addr, sizeof(addr));
		u2ip( local_if, addr);
		memcpy(&x.imr_interface.s_addr, &addr, sizeof(addr));
//		x.imr_ifindex = if_index;
		if (setsockopt(GetSocket(), SOL_IP, IP_ADD_MEMBERSHIP, (char *)&x, sizeof(struct ip_mreq)) == -1)
		{
			Handler().LogError(this, "AddMulticastMembership", Errno, StrError(Errno), LOG_LEVEL_WARNING);
		}
	}
}


void UdpSocket::DropMulticastMembership(const std::string& group,const std::string& local_if,int if_index)
{
#ifdef IPPROTO_IPV6
	if (IsIpv6())
	{
		struct ipv6_mreq x;
		struct in6_addr addr;
		if (u2ip( group, addr ))
		{
			x.ipv6mr_multiaddr = addr;
			x.ipv6mr_interface = if_index;
			if (setsockopt(GetSocket(), IPPROTO_IPV6, IPV6_DROP_MEMBERSHIP, (char *)&x, sizeof(struct ipv6_mreq)) == -1)
			{
				Handler().LogError(this, "DropMulticastMembership", Errno, StrError(Errno), LOG_LEVEL_WARNING);
			}
		}
		return;
	}
#endif
	struct ip_mreq x; // ip_mreqn
	ipaddr_t addr;
	if (u2ip( group, addr ))
	{
		memcpy(&x.imr_multiaddr.s_addr, &addr, sizeof(addr));
		u2ip( local_if, addr);
		memcpy(&x.imr_interface.s_addr, &addr, sizeof(addr));
//		x.imr_ifindex = if_index;
		if (setsockopt(GetSocket(), SOL_IP, IP_DROP_MEMBERSHIP, (char *)&x, sizeof(struct ip_mreq)) == -1)
		{
			Handler().LogError(this, "DropMulticastMembership", Errno, StrError(Errno), LOG_LEVEL_WARNING);
		}
	}
}


#ifdef IPPROTO_IPV6
void UdpSocket::SetMulticastHops(int hops)
{
	if (!IsIpv6())
	{
		Handler().LogError(this, "SetMulticastHops", 0, "Ipv6 only", LOG_LEVEL_ERROR);
		return;
	}
	if (setsockopt(GetSocket(), IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (char *)&hops, sizeof(int)) == -1)
	{
		Handler().LogError(this, "SetMulticastHops", Errno, StrError(Errno), LOG_LEVEL_WARNING);
	}
}


int UdpSocket::GetMulticastHops()
{
	if (!IsIpv6())
	{
		Handler().LogError(this, "SetMulticastHops", 0, "Ipv6 only", LOG_LEVEL_ERROR);
		return -1;
	}
	int hops = 0;
	socklen_t size = sizeof(int);
	if (getsockopt(GetSocket(), IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (char *)&hops, &size) == -1)
	{
		Handler().LogError(this, "GetMulticastHops", Errno, StrError(Errno), LOG_LEVEL_WARNING);
	}
	return hops;
}
#endif // IPPROTO_IPV6




syntax highlighted by Code2HTML, v. 0.9.1