/*
 * Copyright 2012 Daniel Drown <dan-android@drown.org>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * setroute.c - network route configuration
 */
#include <errno.h>
#include <netinet/in.h>
#include <net/if.h>

#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <netlink/handlers.h>
#include <netlink/msg.h>
#include <netlink-types.h>

#include "netlink_msg.h"
#include "setroute.h"
#include "logging.h"
#include "getroute.h"

/* function: if_route
 * create/replace/delete a route
 * ifname      - name of the outbound interface
 * family      - AF_INET or AF_INET6
 * destination - pointer to a struct in_addr or in6_addr for the destination network
 * prefixlen   - bitlength of the network address (example: 24 for AF_INET's 255.255.255.0)
 * gateway     - pointer to a struct in_addr or in6_addr for the gateway to use or NULL for an interface route
 * metric      - route metric (lower is better)
 * mtu         - route-specific mtu or 0 for the interface mtu
 * change_type - ROUTE_DELETE, ROUTE_REPLACE, or ROUTE_CREATE
 */
int if_route(const char *ifname, int family, const void *destination, int prefixlen, const void *gateway, int metric, int mtu, int change_type) {
  int retval;
  struct nl_msg *msg = NULL;
  struct rtmsg rt;
  uint16_t type, flags = 0;
  size_t addr_size;
  uint32_t ifindex;

  addr_size = inet_family_size(family);
  if(addr_size == 0) {
    retval = -EAFNOSUPPORT;
    goto cleanup;
  }

  if (!(ifindex = if_nametoindex(ifname))) {
    retval = -ENODEV;
    goto cleanup;
  }

  memset(&rt, 0, sizeof(rt));
  rt.rtm_family = family;
  rt.rtm_table = RT_TABLE_MAIN;
  rt.rtm_dst_len = prefixlen;
  switch(change_type) {
    case ROUTE_DELETE:
      rt.rtm_scope = RT_SCOPE_NOWHERE;
      type = RTM_DELROUTE;
      break;

    case ROUTE_REPLACE:
      flags = NLM_F_REPLACE;
    case ROUTE_CREATE:
      type = RTM_NEWROUTE;
      flags |= NLM_F_CREATE;
      if(gateway == NULL) {
        rt.rtm_scope = RT_SCOPE_LINK;
      } else {
        rt.rtm_scope = RT_SCOPE_UNIVERSE;
      }
      rt.rtm_type = RTN_UNICAST;
      //RTPROT_STATIC = from administrator's configuration
      //RTPROT_BOOT = from an automatic process
      rt.rtm_protocol = RTPROT_BOOT;
      break;

    default:
      retval = -EINVAL;
      goto cleanup;
  }

  flags |= NLM_F_REQUEST | NLM_F_ACK;

  msg = nlmsg_alloc_rtmsg(type, flags, &rt);
  if(!msg) {
    retval = -ENOMEM;
    goto cleanup;
  }

  if(nla_put(msg, RTA_DST, addr_size, destination) < 0) {
    retval = -ENOMEM;
    goto cleanup;
  }
  if(gateway != NULL)
    if(nla_put(msg, RTA_GATEWAY, addr_size, gateway) < 0) {
      retval = -ENOMEM;
      goto cleanup;
    }
  if(nla_put(msg, RTA_OIF, 4, &ifindex) < 0) {
    retval = -ENOMEM;
    goto cleanup;
  }
  if(nla_put(msg, RTA_PRIORITY, 4, &metric) < 0) {
    retval = -ENOMEM;
    goto cleanup;
  }
  if(mtu > 0 && change_type != ROUTE_DELETE) {
    // MTU is inside an RTA_METRICS nested message
    struct nlattr *metrics = nla_nest_start(msg, RTA_METRICS);
    if(metrics == NULL) {
      retval = -ENOMEM;
      goto cleanup;
    }

    if(nla_put(msg, RTAX_MTU, 4, &mtu) < 0) {
      retval = -ENOMEM;
      goto cleanup;
    }

    nla_nest_end(msg, metrics);
  }

  retval = netlink_sendrecv(msg);

cleanup:
  if(msg)
    nlmsg_free(msg);

  return retval;
}