/*
** SellaNMS
** $Id: bgp.cxx,v 1.16 2005/03/31 18:57:12 sella Exp $
** Copyright (c) 2001-2005 Digital Genesis Software, LLC. All Rights Reserved.
** Released under the GPL Version 2 License.
** http://www.digitalgenesis.com
*/

#ifdef HAVE_CONFIG_H
	#include "config.h"
#endif

#include "bgp.h"
#include "debug.h"
#include <fcntl.h>
#include <netdb.h>
#include <fcntl.h>
#include <ctype.h>
#include <unistd.h>
#include <pthread.h>
#include <linux/if.h>
#include <sys/time.h>
#include <sys/poll.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

BGP::BGP() {
	this->name.append(" bgp");

	has_scheduler = false;
	has_execute = true;
}

BGP::~BGP() {
}

RESULT BGP::readConfig() {
	RESULT result = SUCCESS;

	bgp_t default_bgp;
	unsigned long netmask;
	vector<string> v;
	vector<string>::iterator it;
	Netblock n;
	ConfigNode node;
	bgp_t defaults;
	neighbor_t neighbor;

	/* BGP Neighbor defaults. */
	defaults.bgp_id = htonl(INADDR_ANY);
	defaults.port = htons(MODULE_MONITOR_BGP_PORT_DEFAULT);
	defaults.peer_as = MODULE_MONITOR_BGP_ASN_DEFAULT;
	defaults.version = MODULE_MONITOR_BGP_VERSION_DEFAULT;
	defaults.hold_time = htons(MODULE_MONITOR_BGP_HOLDTIME_DEFAULT);
	defaults.passive = false;

	/* Read configured BGP Neighbor defaults. */
	readNeighborConfig(config, defaults, defaults);

	/* Get our neighbors */
	if (config->assignValues("neighbor", v)) {
		for (it = v.begin(); it != v.end(); ++it) {
			if (n.setNetblock(*it) == SUCCESS) {
				if (n.getNetmask(netmask) == SUCCESS) {
					if (netmask == 0xffff) {
						if (this->config->assignNode(*it, node)) {
							if (readNeighborConfig(&node, neighbor.bgp, defaults) == SUCCESS) {
								this->neighbors.push_front(neighbor);
							}
						}
					}
					LMSG("Adding Netblock: %s", it->c_str());
					this->allowed_neighbors.push_front(n);
				} else {
					LMSG("NOTICE: Failed to obtain netmask for netblock (%s).", it->c_str());
				}
			} else {
				LMSG("NOTICE: Invalid netblock (%s).", it->c_str());
			}
		}
	}

	/* Add and compile input policy. */
	if (config->assignValues("policy input", v)) {
		LMSG("NOTICE: Input policies are not implemented yet.");
		if (inputPolicy.addPolicies(config, v) == FAILURE) {
			LMSG("NOTICE: Failed to add/compile input policies.");
			result = FAILURE;
		}
	}

	/* Add and compile output policy. */
	if (config->assignValues("policy output", v)) {
		if (outputPolicy.addPolicies(config, v) == FAILURE) {
			LMSG("NOTICE: Failed to add/compile output policies.");
			result = FAILURE;
		}
	}

	/* Verify we have required policies. */
	if (outputPolicy.empty()) {
		LMSG("NOTICE: A output policy is required for this module.");
		result = FAILURE;
	}

	/* Configure Storage */
	storage.setConfig(*config);

	return result;
}

RESULT BGP::readNeighborConfig(ConfigNode* node, bgp_t &neighbor, bgp_t &defaults) {
	string str;
	in_addr addr;
	struct servent *s_ent;

	if (node->assignValue("bgp-id", str)) {
		inet_pton(AF_INET, str.c_str(), &addr);
		neighbor.bgp_id = addr.s_addr;
	} else {
		neighbor.bgp_id = defaults.bgp_id;
	}

	if (node->assignValue("port", str)) {
		s_ent = getservbyport(strtoul(str.c_str(), NULL, 10), "tcp");
	} else {
		s_ent = getservbyname("bgp", "tcp");
	}
	if (s_ent) {
		neighbor.port = s_ent->s_port;
	} else {
		neighbor.port = defaults.port;
	}

	if (node->assignValue("peer-as", neighbor.peer_as)) {
		neighbor.peer_as = htons(neighbor.peer_as);
	} else {
		neighbor.peer_as = defaults.peer_as;
	}

	node->assignValue("version", neighbor.version, defaults.version);
	if (neighbor.version != 4) {
		LMSG("NOTICE: Only BGPv4 is supported.");
		neighbor.version = MODULE_MONITOR_BGP_VERSION_DEFAULT;
	}
	
	if (node->assignValue("hold-time", neighbor.hold_time, defaults.hold_time)) {
		neighbor.hold_time = htons(neighbor.hold_time);
	}

	if (node->isNode("non-passive")) {
		neighbor.passive = false;
	} else if (node->isNode("passive")) {
		neighbor.passive = true;
	} else {
		neighbor.passive = defaults.passive;
	}

	return SUCCESS;
}

RESULT BGP::execute() {
	RESULT result = SUCCESS;
	RESULT t_result = SUCCESS;

	pthread_t fsm_thread_id = 0;
	pthread_t storage_thread_id = 0;

	bool running = true;
	unsigned long param_size = 0;

	pthread_mutex_init(&neighbor_mutex, NULL);
	pthread_mutex_init(&update_mutex, NULL);

	/* Create the storage thread */
	if (createThread(storage_thread_id, (void* (*)(void*))BGP::storage_thread, this) != SUCCESS) {
		result = FAILURE;
	}

	/* Create the fsm thread */
	if (createThread(fsm_thread_id, (void* (*)(void*))BGP::fsm_thread, this) != SUCCESS) {
		result = FAILURE;
	}

	/* Request update thread kill itself */
	this->killThread(fsm_thread_id);
	/* Wait for update thread to complete. */
	this->getThreadResult(fsm_thread_id, t_result);
	/* Request storage thread kill itself */
	this->killThread(storage_thread_id);
	/* Wait for storage thread to complete. */
	this->getThreadResult(storage_thread_id, t_result);

	pthread_mutex_destroy(&update_mutex);
	pthread_mutex_destroy(&neighbor_mutex);

	return result;
}

RESULT BGP::fsm_thread(void *args) {
	RESULT result = SUCCESS;	
	pthread_t thread_id = pthread_self();

	BGP *instance = (BGP*) args;
	instance->setThreadStatus(thread_id, THREAD_RUNNING);

	/* Do stuff here. */

	instance->setThreadStatus(thread_id, THREAD_COMPLETED);

	THREAD_RETURN(result);
}

RESULT BGP::storage_thread(void *args) {
	RESULT result = SUCCESS;
	STATUS status;
	pthread_t thread_id = pthread_self();

	BGP *instance = (BGP*) args;
	instance->setThreadStatus(thread_id, THREAD_RUNNING);

	pthread_mutex_t *update_mutex = &(instance->update_mutex);

	bool write = false;
	bool running = true;
	unsigned int len;
	u_int8_t buf[BGP_MSG_MAX_SIZE], *ret_buf;
	string* last_id;
	StorageData data;
	time_t t;
	tm *tm;

	// Until we are asked to stop, store our successes.
	while (running) {
		instance->getThreadStatus(thread_id, status);
		if (status == THREAD_KILLED) {
			running = false;
		}
#if 0
		while (!update_queue->empty()) {
			LMSG("STORAGE - 3");
			/* Fetch Data from queue */
			pthread_mutex_lock(update_mutex);
			pthread_mutex_unlock(update_mutex);

#if 0
			data.setFlag(STORAGEDATA_WRITE | STORAGEDATA_LASTID);
			data.setData("monitor.source", bgp.hostname);

#endif
		}
#endif

#if 0
		if (write) {
			if (instance->write(data) == SUCCESS) {
				data.clear();
				write = false;
			}
		}
#endif
		usleep(USLEEP_NORMLOOP_TIME);
	}

	instance->setThreadStatus(thread_id, THREAD_COMPLETED);

	THREAD_RETURN(result);
}

RESULT BGP::transmit_open(int s) {
#if 0
	int n;
	u_int8_t buf[BGP_MSG_MAX_SIZE];
	u_int8_t *ptr = buf;
	u_int16_t len = htons(BGP_HEADER_SIZE + BGP_OPEN_SIZE);
	u_int8_t type = BGP_MSG_OPEN;

	/* BGP header */
	BUFSET(ptr, 0xff, 16);
	BUFCPY(ptr, len, 2);
	BUFCPY(ptr, type, 1);

	/* BGP open */
	BUFCPY(ptr, this->version, 1);
	BUFCPY(ptr, this->peer_as, 2);
	BUFCPY(ptr, this->hold_time, 2);
	BUFCPY(ptr, this->bgp_id, 4);
	BUFSET(ptr, 0, 1);

	if ((n = send(s, buf, BGP_HEADER_SIZE + BGP_OPEN_SIZE, 0)) == -1) {
		LMSG("Failed to send open");
	} else {
		LMSG("Open away! Send %d/%d", n, BGP_HEADER_SIZE + BGP_OPEN_SIZE);
	}
#endif
}

RESULT BGP::transmit_keepalive(int s) {
#if 0
	u_int8_t buf[BGP_MSG_MAX_SIZE];
	u_int8_t *ptr = buf;
	u_int16_t len = htons(BGP_HEADER_SIZE);
	u_int8_t type = BGP_MSG_KEEPALIVE;

	/* BGP header */
	BUFSET(ptr, 0xff, 16);
	BUFCPY(ptr, len, 2);
	BUFCPY(ptr, type, 1);

	if (send(s, buf, BGP_HEADER_SIZE, 0) == -1) {
		LMSG("Failed to send keepalive");
	} else {
		LMSG("Keepalive away!");
	}
#endif
}

RESULT BGP::handle_packet(int s, u_int8_t* buf, int size) {
	switch (buf[18]) {
		case BGP_MSG_OPEN:
			LMSG("BGP_MSG_OPEN");
			transmit_open(s);
			transmit_keepalive(s);
			break;
		case BGP_MSG_UPDATE:
			LMSG("BGP_MSG_UPDATE");
			break;
		case BGP_MSG_NOTIFY:
			LMSG("BGP_MSG_NOTIFY");
			break;
		case BGP_MSG_KEEPALIVE:
			LMSG("BGP_MSG_KEEPALIVE");
			break;
		case BGP_MSG_ROUTE_REFRESH_01:
			LMSG("BGP_MSG_ROUTE_REFRESH_01");
			break;
		case BGP_MSG_ROUTE_REFRESH:
			LMSG("BGP_MSG_ROUTE_REFRESH");
			break;
		default:
			LMSG("Type is %u", buf[18]);
	}
	
	return SUCCESS;
}

#if 0
RESULT BGP::connect_thread(void *args) {
	RESULT result = SUCCESS;	
	int s, n;
	unsigned long netmask;
	struct fsm_t fsm;
	socklen_t len;
	struct sockaddr_in serv_addr, peer_addr;
	struct timeval tv;
	pthread_t thread_id = pthread_self();
	fd_set rfds;

	BGP *instance = va_arg(arguments, BGP *);
	peer_list_t *peers = &(instance->peers);
	slist<Netblock> *neighbors = &(instance->neighbors);
	pthread_mutex_t *neighbor_mutex = va_arg(arguments, pthread_mutex_t *);

	/* Update our status to THREAD_RUNNING. */
	instance->issueCommand(COMMAND_MODULE_THREADSETSTATUS, thread_id, THREAD_RUNNING);

	/* Create needed sockets. */
	for (slist<Netblock>::iterator it = neighbors->begin(); it != neighbors->end(); ++it) {
		it->getNetmask(netmask);
		if (netmask == 0xffffffff) {
			if ((fsm.addr = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
				LMSG("Failed to create socket in connect_thread.");
			} else {
				pthread_mutex_lock(neighbor_mutex);
				instance->peers.push_front(fsm);
				pthread_mutex_unlock(neighbor_mutex);
			}
		}
	}

	/* Manage sockets */
	while (instance->issueCommand(COMMAND_MODULE_THREADSTATUS, thread_id) != THREAD_KILLED) {
	}

	/* Setup server socket struct */
	len = sizeof(struct sockaddr_in);
	bzero((char*) &serv_addr, len);
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_addr.s_addr = instance->bgp_id;
	serv_addr.sin_port = instance->port;

	if ((s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
		LMSG("Failed to create socket in connect_thread.");
		result = FAILURE;
	} else {
		while (instance->issueCommand(COMMAND_MODULE_THREADSTATUS, thread_id) != THREAD_KILLED) {
			FD_ZERO(&rfds);
			FD_SET(s, &rfds);

			tv.tv_sec = 5;
			tv.tv_usec = 0;

			if (select(s + 1, &rfds, NULL, NULL, &tv) < 1) {
				LMSG("No new incoming connections within 5 seconds.");
			} else {
				len = sizeof(struct sockaddr_in);
				bzero((char*) &peer_addr, len);
				fsm.state = 0;

				if (FD_ISSET(s, &rfds) && (fsm.socket = accept(s, (sockaddr*) &peer_addr, &len)) != -1) {
					LMSG("Got connection from: %s", inet_ntoa(peer_addr.sin_addr));
					/* Is this peer in our list? */
					Netblock n(inet_ntoa(peer_addr.sin_addr));
					for (slist<Netblock>::iterator it = neighbors->begin(); it != neighbors->end(); ++it) {
						if (n.compare(*it) == SUCCESS) {
							fsm.addr = peer_addr.sin_addr;
							fsm.state = BGP_STATE_IDLE;
							fsm.event = 0;
							fsm.send = 0;
							fsm.timer = time(NULL);

							pthread_mutex_lock(neighbor_mutex);
							instance->peers.push_front(fsm);
							pthread_mutex_unlock(neighbor_mutex);
							break;
						}
					}

					if (fsm.state == 0) {
						LMSG("Rejected BGP connection from: %s", inet_ntoa(peer_addr.sin_addr));
						close(fsm.socket);
					}
				}
			}
		}
		close(s);
	}


	/* OLD CODE */

#if 0
	RESULT result = SUCCESS;	
	int s, ps;
	unsigned long netmask;
	struct sockaddr_in serv_addr, peer_addr;
	fsm_t fsm;
	socklen_t len = sizeof(struct sockaddr_in);
	pthread_t thread_id = pthread_self();

	BGP *instance = va_arg(arguments, BGP *);
	instance->issueCommand(COMMAND_MODULE_THREADSETSTATUS, thread_id, THREAD_RUNNING);

	slist<Netblock> *neighbors = &(instance->neighbors);
	peer_list_t *peers = &(instance->peers);

	/* Setup server socket struct */
	bzero((char*) &serv_addr, len);
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_addr.s_addr = instance->bgp_id;
	serv_addr.sin_port = instance->port;

	if ((s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
		LMSG("Failed to create socket in connect_thread.");
		result = FAILURE;
	} else if (bind(s, (sockaddr*) &serv_addr, len) == -1) {
		LMSG("Failed to bind() socket in connect_thread.");
		result = FAILURE;
	} else if (listen(s, 10) == -1) {
		LMSG("Failed to listen() on socket in connect_thread.");
		result = FAILURE;
	} else {
		while (instance->issueCommand(COMMAND_MODULE_THREADSTATUS, thread_id) != THREAD_KILLED) {
			//Loop thru peers, trying to connect to all /32's.
			for (slist<Netblock>::iterator it = neighbors->begin(); it != neighbors->end(); ++it) {
				it->getNetmask(netmask);
				if (netmask == 0xffffffff) {
					bzero((char*) &peer_addr, len);
					peer_addr.sin_family = AF_INET;
					peer_addr.sin_addr.s_addr = 0; //Get network address from neighbors!
					peer_addr.sin_port = instance->port;

					if ((fsm.socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
						LMSG("Failed to create socket in connect_thread.");
						result = FAILURE;
					} else if (connect(fsm.socket, (sockaddr*) &peer_addr, len)) {
						LMSG("Failed to connect socket in connect_thread.");
						result = FAILURE;
					} else {
						fsm.addr = peer_addr.sin_addr;
						fsm.state = BGP_STATE_IDLE;
						fsm.event = 0;
						fsm.send = 0;
						fsm.timer = time(NULL);

						instance->peers.push_front(fsm);
					}
				}
			}

			//
			//select w/timeout of 1 second
			//if !timeout accept
			//  update peer_list
			//else if timeout repeat.






			bzero((char*) &serv_addr, len);
			len = sizeof(struct sockaddr_in);

			/* Process new incoming connections. */
			bool found;
			while ((ps = accept(s, (sockaddr*) &peer_addr, &len)) != -1) {
				LMSG("Got connection from: %s", inet_ntoa(peer_addr.sin_addr));
				/* Is this peer in our list? */
				found = false;
				Netblock n(inet_ntoa(peer_addr.sin_addr));
				for (slist<Netblock>::iterator it = neighbors->begin(); it != neighbors->end(); ++it) {
					if (n.compare(*it) == SUCCESS) {
						found = true;
						break;
					}
				}
				if (found) {
					fsm.socket = ps;
					fsm.addr = peer_addr.sin_addr;
					fsm.state = BGP_STATE_IDLE;
					fsm.event = 0;
					fsm.send = 0;
					fsm.timer = time(NULL);

					instance->peers.push_front(fsm);
				} else {
					LMSG("Rejected BGP connection from: %s", inet_ntoa(peer_addr.sin_addr));
					close(ps);
				}
			}
		}

		close(s);
	}
#endif

	instance->issueCommand(COMMAND_MODULE_THREADSETSTATUS, thread_id, THREAD_COMPLETED);

	THREAD_RETURN(result);
}

RESULT BGP::listen_thread(void *args) {
	RESULT result = SUCCESS;	
	int s, n;
	struct fsm_t fsm;
	socklen_t len;
	struct sockaddr_in serv_addr, peer_addr;
	struct timeval tv;
	pthread_t thread_id = pthread_self();
	fd_set rfds;

	BGP *instance = va_arg(arguments, BGP *);
	peer_list_t *peers = &(instance->peers);
	slist<Netblock> *neighbors = &(instance->neighbors);
	pthread_mutex_t *neighbor_mutex = va_arg(arguments, pthread_mutex_t *);

	/* Update our status to THREAD_RUNNING. */
	instance->issueCommand(COMMAND_MODULE_THREADSETSTATUS, thread_id, THREAD_RUNNING);

	/* Setup server socket struct */
	len = sizeof(struct sockaddr_in);
	bzero((char*) &serv_addr, len);
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_addr.s_addr = instance->bgp_id;
	serv_addr.sin_port = instance->port;

	if ((s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
		LMSG("Failed to create socket in listen_thread.");
		result = FAILURE;
	} else if (bind(s, (sockaddr*) &serv_addr, len) == -1) {
		LMSG("Failed to bind() socket in listen_thread.");
		result = FAILURE;
	} else if (listen(s, 10) == -1) {
		LMSG("Failed to listen() on socket in listen_thread.");
		result = FAILURE;
	} else {
		while (instance->issueCommand(COMMAND_MODULE_THREADSTATUS, thread_id) != THREAD_KILLED) {
			FD_ZERO(&rfds);
			FD_SET(s, &rfds);

			tv.tv_sec = 5;
			tv.tv_usec = 0;

			if (select(s + 1, &rfds, NULL, NULL, &tv) < 1) {
				LMSG("No new incoming connections within 5 seconds.");
			} else {
				len = sizeof(struct sockaddr_in);
				bzero((char*) &peer_addr, len);
				fsm.state = 0;

				if (FD_ISSET(s, &rfds) && (fsm.socket = accept(s, (sockaddr*) &peer_addr, &len)) != -1) {
					LMSG("Got connection from: %s", inet_ntoa(peer_addr.sin_addr));
					/* Is this peer in our list? */
					Netblock n(inet_ntoa(peer_addr.sin_addr));
					for (slist<Netblock>::iterator it = neighbors->begin(); it != neighbors->end(); ++it) {
						if (n.compare(*it) == SUCCESS) {
							fsm.addr = peer_addr.sin_addr;
							fsm.state = BGP_STATE_IDLE;
							fsm.event = 0;
							fsm.send = 0;
							fsm.timer = time(NULL);

							pthread_mutex_lock(neighbor_mutex);
							instance->peers.push_front(fsm);
							pthread_mutex_unlock(neighbor_mutex);
							break;
						}
					}

					if (fsm.state == 0) {
						LMSG("Rejected BGP connection from: %s", inet_ntoa(peer_addr.sin_addr));
						close(fsm.socket);
					}
				}
			}
		}
		close(s);
	}

	instance->issueCommand(COMMAND_MODULE_THREADSETSTATUS, thread_id, THREAD_COMPLETED);

	THREAD_RETURN(result);
}
#endif

/*
** Local Variables:
** c-basic-offset: 3
** tab-width: 3
** End:
** vim600: fdm=marker
** vim: noet ts=3 sw=3
*/
