blob: 3edfe39d593c69e193fe607d3e9cf0b11aa9de1f [file] [log] [blame]
/*
* 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;
}
/* function: set_default_ipv6_route
* copies the default route on an interface (turns an RA route into a static
* route), which is needed to keep the route when forwarding is turned on
* device - interface to be the default route
*/
void set_default_ipv6_route(const char *device) {
struct in6_addr default_6 = IN6ADDR_ANY_INIT;
struct default_route_data default_route;
int status;
void *gateway = NULL;
memset(&default_route, '\0', sizeof(default_route));
default_route.request_family = AF_INET6;
default_route.request_interface_id = if_nametoindex(device);
if(default_route.request_interface_id == 0) {
logmsg(ANDROID_LOG_FATAL, "set_default_ipv6_route failed: no interface %s found", device);
exit(1);
}
status = get_default_route(&default_route);
if(status < 0) {
logmsg(ANDROID_LOG_FATAL, "set_default_ipv6_route/get_default_route failed: returned %d", status);
exit(1);
}
if(!default_route.reply_found_route) {
logmsg(ANDROID_LOG_FATAL, "set_default_ipv6_route/get_default_route failed: no default route found for %s", device);
exit(1);
}
if(default_route.reply_has_gateway) {
gateway = &default_route.reply_gateway.ip6;
}
if((status = if_route(device, AF_INET6, &default_6, 0, gateway, 1, 0, ROUTE_REPLACE)) < 0) {
if(status == -EEXIST) {
logmsg(ANDROID_LOG_WARN,"set_default_ipv6_route/if_route failed due to the route already existing");
} else {
logmsg(ANDROID_LOG_FATAL,"set_default_ipv6_route/if_route failed: %s",strerror(-status));
exit(1);
}
}
}