#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <unistd.h>
#include <fcntl.h>
#include <string.h> /* memcpy */

#include <errno.h>
#include <sys/socket.h>
#include <asm/types.h>
#include <linux/if.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>

#include <arpa/inet.h> /* ntohl htonl */

#include "route_tool.h"



static struct glob {
	unsigned seq;
	unsigned dev;
	unsigned long gateway;
	unsigned long dst;
#ifdef USE_IPV6
	unsigned char gateway6[16];
	unsigned char dst6[16];
#endif
	int *pstatus;
} glob = { 0, };

#if 0 /* AVM only needed for debugmsg */
static int dh = -1;
#endif

#define syserror(x)
#define errmsg(x)
#define debugmsg(x)


/* Utility function for parse rtattr. */
static void parse_rtattr(struct rtattr **tb, int max,
                         struct rtattr *rta, int len)
{
   while (RTA_OK (rta, len)) {
      if (rta->rta_type <= max)
         tb[rta->rta_type] = rta;
      rta = RTA_NEXT (rta, len);
   }
}

static int addattr_l(struct nlmsghdr *n,
                     int maxlen, int type,
                     void *data, int alen)
{
    int len = RTA_LENGTH(alen);
    struct rtattr *rta;

    if (NLMSG_ALIGN(n->nlmsg_len) + len > maxlen)
        return -1;
    rta = (struct rtattr*)(((char*)n) + NLMSG_ALIGN(n->nlmsg_len));
    rta->rta_type = type;
    rta->rta_len = len;
    memcpy(RTA_DATA(rta), data, alen);
    n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + len;
    return 0;
}

static int handle_getroute(struct nlmsghdr *h, int error_code)
{
//   struct routegetinfo rt, *rp = 0;
//   struct rtm_sentget *p, **pp;
   struct rtattr *tb[RTA_MAX + 1];
   struct rtmsg *r;
//   char *dev = 0;
   int len;

   if (getpid() != h->nlmsg_pid || glob.seq != h->nlmsg_seq) return -1;

   if (error_code < 0) {
      debugmsg((dh, "iproute get: ok"));
   } else {
  		*glob.pstatus = error_code;
		return 0;
   }

   len = h->nlmsg_len - NLMSG_LENGTH(sizeof(*r));
   if (len < 0)
     return -1;

   r = NLMSG_DATA (h);

   memset(tb, 0, sizeof(tb));
   parse_rtattr(tb, RTA_MAX, RTM_RTA(r), len);

   if (tb[RTA_OIF]) {
     //  dev = index2ifname(*((int *)RTA_DATA(tb[RTA_OIF])));
     glob.dev = *((int *)RTA_DATA(tb[RTA_OIF]));
   } else {
	  glob.dev = (unsigned)-1;
   }

   if (r->rtm_family == AF_INET) {
	if (tb[RTA_GATEWAY]) {
		glob.gateway = ntohl(*((__u32 *)RTA_DATA(tb[RTA_GATEWAY])));
	} else {
		glob.gateway = 0;
	}
	if (tb[RTA_DST]) {
		glob.dst = ntohl(*((__u32 *)RTA_DATA(tb[RTA_DST])));
	} else {
		glob.dst = 0;
	}
   }
#ifdef USE_IPV6
   else if (r->rtm_family == AF_INET6) {
	if (tb[RTA_GATEWAY]) {
		memcpy(glob.gateway6, RTA_DATA(tb[RTA_GATEWAY]), sizeof(glob.gateway6));
	} else {
		memset(glob.gateway6, 0, sizeof(glob.gateway6));
	}
	if (tb[RTA_DST]) {
		memcpy(glob.dst6, RTA_DATA(tb[RTA_DST]), sizeof(glob.dst6));
	} else {
		memset(glob.dst6, 0, sizeof(glob.dst6));
	}
   }
#endif

  *glob.pstatus = 0;

   return 0;
}


static int check_for_newroute(struct sockaddr_nl *snl, struct nlmsghdr *h)
{
  struct rtmsg *r;
  int len;

  debugmsg((dh, "check_for_newroute"));

  if (h->nlmsg_type != RTM_NEWROUTE) return -1;

  len = h->nlmsg_len - NLMSG_LENGTH(sizeof(*r));
  if (len < 0)
    return -1;

  r = NLMSG_DATA (h);

  if (r->rtm_family != AF_INET && r->rtm_family != AF_INET6)
	 return -1;

  if (handle_getroute(h, -1) == 0) {
	return 0;
  }

  return 0;
}



static void netlink_read(int fd)
{
   char buf[4096];
   struct iovec iov = { buf, sizeof buf };
   struct sockaddr_nl snl;
   struct msghdr msg = { (void *) &snl, sizeof snl, &iov, 1, NULL, 0, 0 };
   struct nlmsghdr *h;
   int count = 5;
   int status;

   debugmsg((dh, "netlink_read start"));

next_message:
   if (--count <= 0)
	  return;

   while ((status = recvmsg(fd, &msg, 0)) <= 0) {
      if (status < 0) {
         if (errno == EINTR)
            continue;
         if (errno == EWOULDBLOCK || errno == EAGAIN)
            return;
         syserror(("recvmsg"));
         return;
      }

      if (status == 0) {
         errmsg(("%s: recvmsg EOF"));
		  close(fd); // TODO
         return;
      }
   }

   debugmsg((dh, "netlink_read: recvmsg ok"));

   if (msg.msg_namelen != sizeof(snl)) {
      errmsg(("sender address length error: length %d", msg.msg_namelen));
      goto next_message;
   }

   for (h = (struct nlmsghdr *) buf; NLMSG_OK(h, status); h = NLMSG_NEXT(h, status)) {
	  debugmsg((dh, "netlink: type %d len %d flags 0x%x seq %d pid %d",
					h->nlmsg_type, h->nlmsg_len, h->nlmsg_flags,
					h->nlmsg_seq, h->nlmsg_pid));
      /*
       * Finish of reading.
       */
      if (h->nlmsg_type == NLMSG_DONE) {
		 debugmsg((dh, "netlink_read: done"));
         goto next_message;
	  }

      /*
       * Error handling.
       */
      if (h->nlmsg_type == NLMSG_ERROR) {
         struct nlmsgerr *err = (struct nlmsgerr *) NLMSG_DATA(h);

         if (h->nlmsg_len < NLMSG_LENGTH(sizeof(struct nlmsgerr))) {
            errmsg(("error: message truncated"));
            goto next_message;
         }

         if ( /* forget_request(&err->msg, -err->error) < 0 && */ handle_getroute(h, -err->error) < 0) {
			if (err->error) {
			   errno = -err->error;
			   syserror(("netlink: no request found for error"));
			} else {
			   errmsg(("netlink: no request found for ack"));
			}
		 }

         /*
          * If the error field is zero, then this is an ACK
          */
         if (err->error == 0) {
            /*
             * return if not a multipart message, otherwise continue
             */
            if (!(h->nlmsg_flags & NLM_F_MULTI)) {
		       debugmsg((dh, "netlink_read: done (not multi)"));
               goto next_message;
            }
            continue;
         }
		 goto next_message;
      }
      if (check_for_newroute(&snl, h) == 0) continue;
   }

   /*
    * After error care.
    */
   if (msg.msg_flags & MSG_TRUNC) {
      errmsg(("error: message truncated"));
   } else if (status) {
      errmsg(("error: data remain size %d", status));
   }
   goto next_message;
}


static int _route_tool_get_dev(int family, unsigned char *addr, unsigned *pdev)
{
   struct {
	  struct nlmsghdr n;
	  struct rtmsg    r;
      char            buf[1024];
   } req;
   struct sockaddr_nl snl;
   int fd, ret;

   fd = socket (AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
   if (fd < 0) {
	  syserror(("netlink_init: socket() failed"));
      return -1;
   }

   ret = fcntl (fd, F_SETFL, O_NONBLOCK);
   if (ret < 0) {
	  syserror(("netlink_init: fcntl(O_NONBLOCK) failed"));
      close(fd);
      return -1;
    }

    memset (&snl, 0, sizeof snl);
    snl.nl_family = AF_NETLINK;
	if (AF_INET == family) {
		snl.nl_groups = RTMGRP_LINK|RTMGRP_IPV4_ROUTE|RTMGRP_IPV4_IFADDR;
	} else {
		snl.nl_groups = RTMGRP_LINK|RTMGRP_IPV6_ROUTE|RTMGRP_IPV6_IFADDR;
	}

    ret = bind (fd, (struct sockaddr *) &snl, sizeof(snl));
    if (ret < 0) {
	   syserror(("netlink_init: bind() failed"));
       close(fd);
       return -1;
    }

   memset(&req, 0, sizeof(req));

   req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
   req.n.nlmsg_flags = NLM_F_REQUEST;
   req.n.nlmsg_type = RTM_GETROUTE;

   req.r.rtm_family = family;

   if (AF_INET == family) {
    req.r.rtm_dst_len = 4*8;
	addattr_l(&req.n, sizeof(req), RTA_DST, addr, 4);
   }
#ifdef USE_IPV6
   else if (AF_INET6 == family) {
    req.r.rtm_dst_len = 16*8;
	addattr_l(&req.n, sizeof(req), RTA_DST, addr, 16);
   }
#endif

   req.n.nlmsg_pid = getpid();
   req.n.nlmsg_seq = ++glob.seq;

   memset (&snl, 0, sizeof(snl));
   snl.nl_family = AF_NETLINK;
   if (sendto (fd,
			   (void *) &req, req.n.nlmsg_len, 0,
			   (struct sockaddr *) &snl, sizeof(snl)) < 0) {
	  syserror(("netlink: sendto: failed"));
   close(fd);
	  return -1;
   }

	int status = -99;
   glob.pstatus = &status;

   do {
	  netlink_read(fd);
   } while(status == -99);

   close(fd);

   if (status > 0) return -1;
	if (glob.dev == (unsigned)-1) return -1;

   *pdev = glob.dev;
   return 0;
}

#ifdef USE_IPV6
int route_tool_get_ipv6_dev(unsigned char *ipv6addr /* 16 byte */, unsigned *pdev, unsigned char *gateway6, unsigned char *dst6)
{
	int ret = _route_tool_get_dev(AF_INET6, ipv6addr, pdev);
	if (0 == ret) {
		memcpy(gateway6, glob.gateway6, 16);
		memcpy(dst6, glob.dst6, 16);
	}
	return ret;
}
#endif

int route_tool_get_dev(unsigned long ipaddr/*hostorder*/, unsigned *pdev, unsigned long *pgateway, unsigned long *pdst)
{
   __u32 addr = htonl(ipaddr);

	int ret = _route_tool_get_dev(AF_INET, (unsigned char *)&addr, pdev);
	if (ret == 0) {
		*pgateway = glob.gateway;
		*pdst = glob.dst;
	}
	return ret;
}