#include "VrEthMonitor.h"
#include "IVrUtils.h"

#include <thread>

#ifndef _WIN32
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <asm/types.h>
#include <arpa/inet.h>
#include <linux/route.h>
#include <unistd.h>
#include <linux/if.h>
#include <linux/rtnetlink.h>
#include <stdio.h>
#include <mutex>
#include <ifaddrs.h>
#endif

CVrEthMonitor::CVrEthMonitor()
	: m_fNotify(nullptr)
	, m_pParam(nullptr)
	, m_bRunning(false)
{
}

CVrEthMonitor::~CVrEthMonitor()
{
}

/// @brief
/// 开始监控
/// @return 成功返回TRUE
int CVrEthMonitor::StartMonitor(NetNotify func, void* pParam)
{
	m_bRunning = true;
	std::thread runMonitor(std::bind(&CVrEthMonitor::_MonitorTask, this));
	runMonitor.detach();
	m_fNotify = func;
	m_pParam = pParam;
	return SUCCESS;
}

/// @brief
/// 停止监控
void CVrEthMonitor::StopMonitor()
{
	m_bRunning = false;
}
void CVrEthMonitor::_MonitorTask()
{
#ifndef _WIN32
	int socket_fd;
	int err = 0;
	int read_r;
	struct sockaddr_nl sa;
	struct nlmsghdr *nh;

	const int len = 2048;
	char buff[len];
	// signal(SIGINT, intHandler);

	/*打开NetLink Socket*/
	socket_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
	setsockopt(socket_fd, SOL_SOCKET, SO_RCVBUF, &len, sizeof(len));

	/*设定接收类型并绑定Socket*/
	bzero(&sa, sizeof(sa));
	sa.nl_family = AF_NETLINK;
	sa.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV4_ROUTE | RTMGRP_IPV6_IFADDR | RTMGRP_IPV6_ROUTE;
	bind(socket_fd, (struct sockaddr *) &sa, sizeof(sa));
	
	while (m_bRunning)
	{
		fd_set readFD;
		FD_SET(socket_fd, &readFD);
		struct timeval waitTime = { 5, 0 };
		int nCount = select(socket_fd + 1, &readFD, nullptr, nullptr, &waitTime);
		if (nCount > 0)
		{
			read_r = read(socket_fd, buff, sizeof(buff));
			for (nh = (struct nlmsghdr *) buff; NLMSG_OK(nh, read_r); nh = NLMSG_NEXT(nh, read_r))
			{
				switch (nh->nlmsg_type) {
				default:
					/*收到些奇怪的信息*/
					break;
				case NLMSG_DONE:
				case NLMSG_ERROR:
					break;
				case RTM_NEWLINK:
				case RTM_DELLINK:
					_ifinfomsg(nh);
					break;
				case RTM_NEWADDR:
				case RTM_DELADDR:
					_ifaddrmsg(nh);
					break;
				case RTM_NEWROUTE:
				case RTM_DELROUTE:
					_rtmsg(nh);
					break;
				}
			}
		}
	}

	close(socket_fd);

#endif // !_WIN32
}


/// 解析RTA,并存入tb
void CVrEthMonitor::_parse_rtattr(struct rtattr **tb, int max, struct rtattr *attr, int len)
{
#ifndef _WIN32
	for (; RTA_OK(attr, len); attr = RTA_NEXT(attr, len)) {
		if (attr->rta_type <= max) {
			tb[attr->rta_type] = attr;
		}
	}
#endif // !_WIN32
}

///显示连接信息
///当网卡变动的时候触发这个信息,例如插/拔网线,增/减网卡设备,启用/禁用接口等.
void CVrEthMonitor::_ifinfomsg(struct nlmsghdr *nh)
{
#ifndef _WIN32
	int len;
	struct rtattr *tb[IFLA_MAX + 1];
	struct ifinfomsg *ifinfo;
	bzero(tb, sizeof(tb));
	ifinfo = (ifinfomsg *)NLMSG_DATA(nh);
	len = nh->nlmsg_len - NLMSG_SPACE(sizeof(*ifinfo));
	_parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifinfo), len);

	//LOG_DEBUG("%s %s: %s \n", (char *)RTA_DATA(tb[IFLA_IFNAME]), (nh->nlmsg_type == RTM_NEWLINK) ? "NEWLINK" : "DELLINK", (ifinfo->ifi_flags & IFF_LOWER_UP) ? "up" : "down");
	std::string sName = (char *)RTA_DATA(tb[IFLA_IFNAME]);

	if (sName.find("eth") != std::string::npos)
	{
		if (ifinfo->ifi_flags & IFF_LOWER_UP)
		{
			m_fNotify(sName, NET_STATUS_UP, m_pParam);
		}
		else
		{
			m_fNotify(sName, NET_STATUS_DOWN, m_pParam);
		}
	}

#endif // !_WIN32
}

///显示地址信息
///当地址变动的时候触发这个信息,例如通过DHCP获取到地址后
void CVrEthMonitor::_ifaddrmsg(struct nlmsghdr *nh)
{
#ifndef _WIN32
	int len;
	struct rtattr *tb[IFA_MAX + 1];
	struct ifaddrmsg *ifaddr;
	char tmp[256];
	bzero(tb, sizeof(tb));
	ifaddr = (ifaddrmsg *)NLMSG_DATA(nh);
	len = nh->nlmsg_len - NLMSG_SPACE(sizeof(*ifaddr));
	_parse_rtattr(tb, IFA_MAX, IFA_RTA(ifaddr), len);

	if (tb[IFA_LABEL] != NULL && tb[IFA_ADDRESS] != NULL && nh->nlmsg_type == RTM_NEWADDR)
	{
		inet_ntop(ifaddr->ifa_family, RTA_DATA(tb[IFA_ADDRESS]), tmp, sizeof(tmp));	
	}
	std::string sName = (char *)RTA_DATA(tb[IFLA_IFNAME]);
	m_fNotify(sName, NET_STATUS_STATUS_CHANGE, m_pParam);

#endif
}

/**
* 显示路由信息
* 当路由变动的时候触发这个信息
*/
void CVrEthMonitor::_rtmsg(struct nlmsghdr *nh)
{
#ifndef _WIN32
	int len;
	struct rtattr *tb[RTA_MAX + 1];
	struct rtmsg *rt;
	char tmp[256];
	bzero(tb, sizeof(tb));
	rt = (rtmsg *)NLMSG_DATA(nh);
	len = nh->nlmsg_len - NLMSG_SPACE(sizeof(*rt));
	_parse_rtattr(tb, RTA_MAX, RTM_RTA(rt), len);

	if (tb[RTA_DST] != NULL) {
		inet_ntop(rt->rtm_family, RTA_DATA(tb[RTA_DST]), tmp, sizeof(tmp));
	}
	if (tb[RTA_SRC] != NULL) {
		inet_ntop(rt->rtm_family, RTA_DATA(tb[RTA_SRC]), tmp, sizeof(tmp));
	}
	if (tb[RTA_GATEWAY] != NULL) {
		inet_ntop(rt->rtm_family, RTA_DATA(tb[RTA_GATEWAY]), tmp, sizeof(tmp));

		if (nh->nlmsg_type == RTM_NEWROUTE) {
			m_fNotify("", NET_STATUS_ROUTE_ADD, m_pParam);
		} else {
			m_fNotify("", NET_STATUS_ROUTE_DEL, m_pParam);
		}
		//system("ip rule add from all lookup main pref 0");
	}

#endif // !_WIN32
}

bool VrCreateEthMonitor(IVrEthMonitor** ppEthMonitor)
{
	CVrEthMonitor* pObj = new CVrEthMonitor;
	*ppEthMonitor = pObj;
	return true;
}