/*

                                   _JNJ`                        
                               .JNMH`                           
                              JMMF`       `;.                   
                            .NMM)          `MN.                 
                            MMM)            (MML                
                           (MMM`             MMML               
                     M  I  D  N  I  G  H  T    C  o  D  E       
                           (NMMF             MHNH               
                            NMML            .MMM                
                             NMML          .NMH                 
                              4MMNL       .#F                   
                               `4HNNL_   `                      
                                   `"""`                        

                          Copyright (C) 2004-2005
       "Ian (Larry) Latter" <ian dot latter at midnightcode dot org>

    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, and mirrored at the Midnight Code web
    site; as at version 2 of the License only.
  
    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
    (version 2) along with this program; if not, write to the Free
    Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
    02111-1307 USA, or see http://midnightcode.org/gplv2.txt

*/

/* midnightcode-route.c :: Midnight Code network route library		*/


#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
#include <unistd.h>
#include <netdb.h>
#include <arpa/inet.h>

#include <net/route.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <net/if.h>
#include <asm/types.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <sys/socket.h>


#include "midnightcode-list.h"
#include "midnightcode-route.h"


int
midnightcode_route_listrecv(int sockfd, char *msgbuf, uint msgseq, uint msgpid) {
  struct nlmsghdr *msg_nlhdr;
  /* struct nlmsgerr *msg_nlerr; */
  int msglen, recvlen, term;


  term = 0;

  msglen = 0;
  do {
    if((recvlen = recv(sockfd, msgbuf, 8192 - msglen, 0)) < 0)
      return -1;

    msg_nlhdr = (struct nlmsghdr *)msgbuf;

    if(NLMSG_OK(msg_nlhdr, (uint)recvlen) == 0)
      return -1;

    if(msg_nlhdr->nlmsg_type == NLMSG_ERROR) {
      /* msg_nlerr = (struct nlmsgerr *)NLMSG_DATA(msg_nlhdr); */
      return -1;
    }

    if (msg_nlhdr->nlmsg_type == NLMSG_DONE) {
      term++;
      break;
    } else {
      msgbuf += recvlen;
      msglen += recvlen;
    }

    if((msg_nlhdr->nlmsg_flags & NLM_F_MULTI) == 0) {
      term++;
      break;
    }

  } while ((msg_nlhdr->nlmsg_seq != msgseq) ||
           (msg_nlhdr->nlmsg_pid != msgpid) ||
           (term == 0));

  return msglen;
}


int
midnightcode_route_listadd(struct midnightcode_route_head_t * elements,
                           struct midnightcode_route_element_t * now_element,
                           void * void_nlhdr) {
  struct nlmsghdr *msg_nlhdr;
  struct rtmsg *msg_rt;
  struct rtattr *msg_rtattr;
  int msgrtlen;


  msg_nlhdr = void_nlhdr;
  msg_rt = (struct rtmsg *)NLMSG_DATA(msg_nlhdr);
  msg_rtattr = (struct rtattr *)RTM_RTA(msg_rt);
  msgrtlen = RTM_PAYLOAD(msg_nlhdr);

  now_element = malloc(sizeof(struct midnightcode_route_element_t));
  memset(now_element, 0, sizeof(struct midnightcode_route_element_t));

  for(; RTA_OK(msg_rtattr, msgrtlen);
      msg_rtattr = RTA_NEXT(msg_rtattr, msgrtlen)) {

    switch(msg_rtattr->rta_type) {

      case RTA_DST:
        now_element->ipv4_destination = (*(struct in_addr *)
                                                    RTA_DATA(msg_rtattr));
	inet_ntop(AF_INET, RTA_DATA(msg_rtattr), 
                  now_element->ipv4_destination_s,
                  sizeof(now_element->ipv4_destination_s));
        break;

      case RTA_GATEWAY:
        now_element->ipv4_gateway = (*(struct in_addr *)RTA_DATA(msg_rtattr));
	inet_ntop(AF_INET, RTA_DATA(msg_rtattr), 
                  now_element->ipv4_gateway_s,
                  sizeof(now_element->ipv4_gateway_s));
        break;

      case RTA_IIF:
        now_element->in_ifindex = *(int *)RTA_DATA(msg_rtattr);
        /* TODO: set in_ifname */
        break;

      case RTA_METRICS:
        now_element->metric = *(int *)RTA_DATA(msg_rtattr);
        break;

      case RTA_OIF:
        now_element->out_ifindex = *(int *)RTA_DATA(msg_rtattr);
        /* TODO: set out_ifname */
        break;

      case RTA_PREFSRC:
        now_element->ipv4_prefsource = (*(struct in_addr *)RTA_DATA(msg_rtattr));
	inet_ntop(AF_INET, RTA_DATA(msg_rtattr), 
                  now_element->ipv4_prefsource_s,
                  sizeof(now_element->ipv4_prefsource_s));
        break;

      case RTA_PRIORITY:
        now_element->priority = *(int *)RTA_DATA(msg_rtattr);
        break;

      case RTA_SRC:
        now_element->ipv4_source = (*(struct in_addr *)RTA_DATA(msg_rtattr));
	inet_ntop(AF_INET, RTA_DATA(msg_rtattr), 
                  now_element->ipv4_source_s,
                  sizeof(now_element->ipv4_source_s));
        break;

      default:
        break;

    }
  }

  if(inet_lnaof(now_element->ipv4_destination) == 0)
    snprintf(now_element->ipv4_destination_s,
             sizeof(now_element->ipv4_destination_s) - 1, "0.0.0.0");
  if(inet_lnaof(now_element->ipv4_gateway) == 0)
    snprintf(now_element->ipv4_gateway_s, sizeof(now_element->ipv4_gateway_s) - 1,
             "0.0.0.0");
  if(inet_lnaof(now_element->ipv4_prefsource) == 0)
    snprintf(now_element->ipv4_prefsource_s,
             sizeof(now_element->ipv4_prefsource_s) - 1, "0.0.0.0");
  if(inet_lnaof(now_element->ipv4_source) == 0)
    snprintf(now_element->ipv4_source_s, sizeof(now_element->ipv4_source_s) - 1,
             "0.0.0.0");
  if(inet_lnaof(now_element->ipv4_destination) == 0 &&
     inet_lnaof(now_element->ipv4_prefsource) == 0 &&
     inet_lnaof(now_element->ipv4_gateway) != 0)
    now_element->is_defaultroute = 1;

  midnightcode_list_append(elements, now_element);

  return 1;
}


int
midnightcode_route_list(struct midnightcode_route_head_t * elements) {
  int sockfd;
  struct midnightcode_route_element_t * now_element;
  struct nlmsghdr *msg_nlhdr;
  struct rtmsg *msg_rt;
  char msgbuf[8192];
  int msgpid, msgseq, msglen;


  msgpid = (int)getpid();
  msgseq = 0;

  if((sockfd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE)) < 0)
    return -1;

  memset(msgbuf, 0, sizeof(msgbuf));
  msg_nlhdr = (struct nlmsghdr *)msgbuf;
  msg_nlhdr->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
  msg_nlhdr->nlmsg_type = RTM_GETROUTE;
  msg_nlhdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
  msg_nlhdr->nlmsg_seq = msgseq++;
  msg_nlhdr->nlmsg_pid = msgpid;

  msg_rt = (struct rtmsg *)NLMSG_DATA(msg_nlhdr);
  msg_rt->rtm_table = RT_TABLE_DEFAULT;
  msg_rt->rtm_protocol = RTPROT_UNSPEC;
  msg_rt->rtm_scope = RT_SCOPE_UNIVERSE;
  msg_rt->rtm_family = AF_INET;
  msg_rt->rtm_type = RTN_UNICAST;

  if(send(sockfd, msg_nlhdr, msg_nlhdr->nlmsg_len, 0) < 0)
    return -1;

  if((msglen = midnightcode_route_listrecv(sockfd, msgbuf, msgseq, msgpid)) < 0)
    return -1;

  now_element = NULL;
  for(; NLMSG_OK(msg_nlhdr, (uint)msglen);
      msg_nlhdr = NLMSG_NEXT(msg_nlhdr, msglen))
    midnightcode_route_listadd(elements, now_element, (void *)msg_nlhdr);

  close(sockfd);

  return 0;
}


int
midnightcode_route_default(struct midnightcode_route_head_t *elements) {
  struct midnightcode_route_element_t * now_element;
  struct midnightcode_route_element_t * old_element;


  if(midnightcode_route_list(elements) < 0)
    return -1;

  old_element = NULL;
  midnightcode_list_for_each(now_element, elements) {
    if(old_element != NULL) {
      free(old_element);
      old_element = NULL;
    }
    if(now_element->is_defaultroute != 1) {
      midnightcode_list_remove(elements, now_element);
      old_element = now_element;
    }
  }
  if(old_element != NULL)
    free(old_element);

  return 0;
}

