// [License]
// The Ariba-Underlay Copyright
//
// Copyright (c) 2008-2009, Institute of Telematics, Universität Karlsruhe (TH)
//
// Institute of Telematics
// Universität Karlsruhe (TH)
// Zirkel 2, 76128 Karlsruhe
// Germany
//
// 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.
//
// THIS SOFTWARE IS PROVIDED BY THE INSTITUTE OF TELEMATICS ``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 ARIBA PROJECT 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.
//
// The views and conclusions contained in the software and documentation
// are those of the authors and should not be interpreted as representing
// official policies, either expressed or implied, of the Institute of
// Telematics.
// [License]

#include "NetworkChangeDetection.h"

namespace ariba {
namespace communication {

use_logging_cpp(NetworkChangeDetection);
SystemEventType NetworkChangeDetectionEventType("NetworkChangeDetectionEventType");

NetworkChangeDetection::NetworkChangeDetection()
	: running( false ), monitoringThread( NULL ), routingSocket( -1 ){
	startMonitoring();
}

NetworkChangeDetection::~NetworkChangeDetection(){
	stopMonitoring();
}

void NetworkChangeDetection::registerNotification(NetworkChangeInterface* callback){

	RegistrationList::iterator i = find(
		registrations.begin(), registrations.end(), callback );

	if( i == registrations.end() )
		registrations.push_back( callback );
}

void NetworkChangeDetection::unregisterNotification(NetworkChangeInterface* callback){

	RegistrationList::iterator i = find(
		registrations.begin(), registrations.end(), callback );

	if( i != registrations.end() )
		registrations.erase( i );
}

void NetworkChangeDetection::startMonitoring(){

	logging_debug( "starting network change monitoring ..." );

	running = true;
	monitoringThread = new boost::thread(
		boost::bind(&NetworkChangeDetection::monitoringThreadFunc, this) );
}

void NetworkChangeDetection::stopMonitoring(){

	logging_debug( "stopping network change monitoring ..." );

	// trigger the blocking read to end
	running = false;

	shutdown( routingSocket, SHUT_RDWR );
	close( routingSocket );

	// end the monitoring thread
	monitoringThread->join();

	delete monitoringThread;
	monitoringThread = NULL;
}

void NetworkChangeDetection::monitoringThreadFunc(NetworkChangeDetection* obj){

	//
	// Most information about this routing socket interface is available
	// when searching for NETLINK_ROUTE and in http://www.faqs.org/rfcs/rfc3549.html
	// A small program that uses this stuff is ifplugd, nice reference for
	// getting network interface notifications using routing sockets
	// (e.g. http://ifplugd.sourcearchive.com/documentation/0.26/ifmonitor_8c-source.html)
	// And the best resource is the linux code ... http://www.srcdoc.com/linux_2.2.26/rtnetlink_8h-source.html
	// More:
	//	http://m.linuxjournal.com/article/8498
	// 	http://lkml.indiana.edu/hypermail/linux/net/0508.2/0016.html
	//	http://infrahip.hiit.fi/hipl/release/1.0.1/hipl.1.0.1/hipd/netdev.c
	// 	http://www.google.de/codesearch?hl=de&q=rtmsg_ifinfo+show:Ndy7Mq7IdCw:aylASy5mtF0:ClfaxlLp-PU&source=universal&cs_p=ftp://ftp.kernel.org/pub/linux/kernel/v2.4/linux-2.4.18.tar.gz&cs_f=linux/net/core/rtnetlink.c#l490
	//	http://www.google.com/codesearch?hl=en&q=RTM_NEWADDR+NETLINK_ROUTE+show:eUekvGbF94M:iCwmLwtnVbU:38qoeYp--0A&sa=N&cd=1&ct=rc&cs_p=ftp://ftp.stacken.kth.se/pub/arla/arla-0.42.tar.gz&cs_f=arla-0.42/lib/roken/getifaddrs.c
	//	http://www.google.com/codesearch?hl=en&q=RTMGRP_LINK+rtm_newaddr+nl_groups+show:sUV8mV--Lb4:gk0jx_xv0Yg:ZB9MN7Fgkgg&sa=N&cd=1&ct=rc&cs_p=http://freshmeat.net/redir/strongswan/60428/url_bz2/strongswan-4.1.2.tar.bz2&cs_f=strongswan-4.1.4/src/charon/kernel/kernel_interface.c#l689
	//

	//
	// open the netlink routing socket, don't need raw sockets here!
	//

	obj->routingSocket = socket( PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE );
	if( obj->routingSocket < 0 ){
		logging_error("could not connect to routing socket: " +
					string(strerror(errno)));
		return;
	}

	//
	// set a socket read timeout. this is actually bad, but closing
	// the blocking socket using shutdown, close, or writing EOF to
	// the socket did not work. this seems to be a bug related to
	// routing sockets that will not close when in blocking mode
	//

	struct timeval tv;
	tv.tv_sec = 1;
	tv.tv_usec = 0;

	setsockopt( obj->routingSocket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv) );

	//
	// bind the netlink routing socket to our local address
	// for the nl_groups, see http://www.cs.fsu.edu/~baker/devices/lxr/http/source/linux/include/linux/rtnetlink.h#L520
	// we listen on ipv4/6 new/delete address and link up/down notifications
	//

	struct sockaddr_nl addr;
	memset( &addr, 0, sizeof(addr) );
	addr.nl_family = AF_NETLINK;
	addr.nl_groups = RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR | RTMGRP_LINK;
	addr.nl_pid    = getpid();

	int ret = bind( obj->routingSocket, (struct sockaddr*)&addr, sizeof(addr) );
	if( ret < 0 ){
		close( obj->routingSocket );
		logging_error( "could not bind routing socket: " + string(strerror(errno)) );
		return;
	}

	//
	// read from the netlink routing socket
	//

	logging_debug( "network change monitoring started" );

	while( obj->running ){

		char buffer[1024];
		struct nlmsghdr* header = (struct nlmsghdr*)buffer;

		int bytesRead = recv( obj->routingSocket, &buffer, sizeof(buffer), 0 );

		if( bytesRead < 0 ){

			// socket timeout, just continue
			if( errno == EWOULDBLOCK ) continue;

			// triggered by shutdown() function in stopMonitoring
			if( errno == EBADF ) return;

			// gracefull exit by signals
			if( errno == EAGAIN || errno == EINTR ) return;

			// all others are some kind of error
			logging_error( "could not read from routing socket: " +
							string(strerror(errno)) );
			break;
		}

		for( ; bytesRead > 0; header = NLMSG_NEXT(header, bytesRead)) {
			if (	!NLMSG_OK(header, bytesRead) ||
				(size_t) bytesRead < sizeof(struct nlmsghdr) ||
				(size_t) bytesRead < header->nlmsg_len) {
				continue;
			}

			//
			// here we evaluate the routing netlink message. for, the following
			// messages are of interest:
			// 	RTM_NEWLINK, RTM_DELLINK -> interface up/down
			//	RTM_NEWADDR, RTM_DELADDR -> new address on iface, address deleted from iface
			// not interesting are:
			//	RTM_GETLINK, RTM_GETADDR
			//	RTM_NEWROUTE, RTM_DELROUTE, RTM_GETROUTE
			//

			NetworkChangeInterface::NetworkChangeInfo changeInfo;

			switch( header->nlmsg_type ){

				//
				// handle network interface up/down notifications
				// --> link layer notifications
				//

				case RTM_NEWLINK:
				case RTM_DELLINK:

					changeInfo = obj->extractInterfaceEvent( header );
					break;

				//
				// handle network address configuration changes
				// --> network layer notifications
				//

				case RTM_NEWADDR:
				case RTM_DELADDR:

					changeInfo = obj->extractAddressEvent( header );
					break;

				//
				// other RTM message are ignored in case
				// we did subscribe using nl_groups (see above)
				//

				default: break;

			} // switch( header->nlmsg_type )

			logging_info( "network change detected: [" << changeInfo << "]" );

			// put noficiation event into the system queue for main
			// thread synchronization. will be handled in NetworkChangeDetection::handleSystemEvent()
			// that will run synchronized with the main thread

			SystemQueue::instance().scheduleEvent(
				SystemEvent( obj, NetworkChangeDetectionEventType,
					new NetworkChangeInterface::NetworkChangeInfo(changeInfo)));

		} // for( ; bytesRead > 0; header = NLMSG_NEXT(header, bytesRead))
	} // while( running )

	logging_debug( "network change monitoring stopped" );
}

NetworkChangeInterface::NetworkChangeInfo NetworkChangeDetection::extractInterfaceEvent(struct nlmsghdr* header){
	NetworkChangeInterface::NetworkChangeInfo changeInfo;

	//
	// handle network interface up/down notifications
	// the differentiation between up/down is _not_
	// made using new/del-link in the message!
	//

	if( header->nlmsg_type == RTM_NEWLINK ){
		logging_debug("network change message RTM_NEWLINK");
	}else if( header->nlmsg_type == RTM_DELLINK ){
		logging_debug("network change message RTM_DELLINK");
	}

	if( header->nlmsg_len < NLMSG_LENGTH(sizeof(struct ifinfomsg)) ){
		logging_error( "netlink packet soo small for ifinfomsg" );
		return changeInfo;
	}

	//
	// get the interface index and through this
	// further information about the interface
	//

	struct ifinfomsg* detailIface = (struct ifinfomsg*)NLMSG_DATA(header);
	changeInfo.interface = networkInformation.getInterface( detailIface->ifi_index );

	//
	// check whether device is now up or down
	//

	if( changeInfo.interface.isUp && changeInfo.interface.isRunning )
		changeInfo.type = NetworkChangeInterface::EventTypeInterfaceUp;
	else
		changeInfo.type = NetworkChangeInterface::EventTypeInterfaceDown;


	return changeInfo;
}

NetworkChangeInterface::NetworkChangeInfo NetworkChangeDetection::extractAddressEvent(struct nlmsghdr* header){
	NetworkChangeInterface::NetworkChangeInfo changeInfo;

	//
	// handle network address configuration changes
	// addresses are added/removed from an interface
	//

	if( header->nlmsg_type == RTM_NEWADDR ){
		logging_debug("network change message RTM_NEWADDR");
		changeInfo.type = NetworkChangeInterface::EventTypeAddressNew;
	}else if( header->nlmsg_type == RTM_DELADDR ){
		logging_debug("network change message RTM_DELADDR");
		changeInfo.type = NetworkChangeInterface::EventTypeAddressDelete;
	}

	struct ifaddrmsg* detailAddr = (struct ifaddrmsg*)NLMSG_DATA(header);
	changeInfo.interface = networkInformation.getInterface( detailAddr->ifa_index );

	return changeInfo;
}

void NetworkChangeDetection::handleSystemEvent(const SystemEvent& event){

	NetworkChangeInterface::NetworkChangeInfo* changeInfo = event.getData<NetworkChangeInterface::NetworkChangeInfo>();
	const NetworkChangeDetection* obj = (const NetworkChangeDetection*)event.getListener();

	RegistrationList::const_iterator i = obj->registrations.begin();
	RegistrationList::const_iterator iend = obj->registrations.end();

	for( ; i != iend; i++ )
		(*i)->onNetworkChange(*changeInfo);

	delete changeInfo;
}

}} // namespace ariba, communication
