/* This file is part of Netsukuku * (c) Copyright 2005 Andrea Lo Pumo aka AlpT * * This source code 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; either version 2 of the License, * or (at your option) any later version. * * This source code 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. * Please refer to the GNU Public License for more details. * * You should have received a copy of the GNU Public License along with * this source code; if not, write to: * Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * - * * Various parts are ripped from iproute2/iproute.c * written by Alexey Kuznetsov, . */ #include "includes.h" #include "if.h" #include "libnetlink.h" #include "inet.h" #include "krnl_route.h" #include "libnetlink.h" #include "ll_map.h" #include "common.h" #ifdef LINUX_2_6_14 #include #define NTK_MULTIPATH_ALGO IP_MP_ALG_WRANDOM #endif static struct { int tb; int flushed; char *flushb; int flushp; int flushe; struct rtnl_handle *rth; int protocol, protocolmask; int scope, scopemask; int type, typemask; int tos, tosmask; int iif, iifmask; int oif, oifmask; int realm, realmmask; inet_prefix rprefsrc; inet_prefix rvia; inet_prefix rdst; inet_prefix mdst; inet_prefix rsrc; inet_prefix msrc; } filter; void route_reset_filter() { setzero(&filter, sizeof(filter)); filter.mdst.bits = -1; filter.msrc.bits = -1; } int route_exec(int route_cmd, int route_type, int route_scope, unsigned flags, inet_prefix * src, inet_prefix * to, struct nexthop *nhops, char *dev, u_char table); int route_add(ROUTE_CMD_VARS) { return route_exec(RTM_NEWROUTE, type, scope, NLM_F_CREATE | NLM_F_EXCL, src, to, nhops, dev, table); } int route_del(ROUTE_CMD_VARS) { return route_exec(RTM_DELROUTE, type, scope, 0, src, to, nhops, dev, table); } /*If it doesn't exist, CREATE IT! de ih oh oh*/ int route_replace(ROUTE_CMD_VARS) { return route_exec(RTM_NEWROUTE, type, scope, NLM_F_REPLACE | NLM_F_CREATE, src, to, nhops, dev, table); } int route_change(ROUTE_CMD_VARS) { return route_exec(RTM_NEWROUTE, type, scope, NLM_F_REPLACE, src, to, nhops, dev, table); } int route_append(ROUTE_CMD_VARS) { return route_exec(RTM_NEWROUTE, type, scope, NLM_F_CREATE | NLM_F_APPEND, src, to, nhops, dev, table); } int add_nexthops(struct nlmsghdr *n, struct rtmsg *r, struct nexthop *nhop) { char buf[1024]; struct rtattr *rta = (void *) buf; struct rtnexthop *rtnh; int i = 0, idx; rta->rta_type = RTA_MULTIPATH; rta->rta_len = RTA_LENGTH(0); rtnh = RTA_DATA(rta); if (!nhop[i + 1].dev) { /* Just one gateway */ r->rtm_family = nhop[i].gw.family; addattr_l(n, sizeof(struct rt_request), RTA_GATEWAY, &nhop[i].gw.data, nhop[i].gw.len); if (nhop[0].dev) { if ((idx = ll_name_to_index(nhop[0].dev)) == 0) { error(ERROR_MSG "Device \"%s\" doesn't really exist\n", ERROR_POS, nhop[0].dev); return -1; } addattr32(n, sizeof(struct rt_request), RTA_OIF, idx); } return 0; } #if 0 /* We have more than one nexthop, equalize them */ req.rt.rtm_flags |= RTM_F_EQUALIZE; #endif while (nhop[i].dev != 0) { setzero(rtnh, sizeof(*rtnh)); rtnh->rtnh_len = sizeof(*rtnh); rta->rta_len += rtnh->rtnh_len; if (nhop[i].gw.len) { if (nhop[i].gw.family == AF_INET) rta_addattr32(rta, 4096, RTA_GATEWAY, nhop[i].gw.data[0]); else if (nhop[i].gw.family == AF_INET6) rta_addattr_l(rta, 4096, RTA_GATEWAY, nhop[i].gw.data, nhop[i].gw.len); rtnh->rtnh_len += sizeof(struct rtattr) + nhop[i].gw.len; } if (nhop[i].dev) if ((rtnh->rtnh_ifindex = ll_name_to_index(nhop[i].dev)) == 0) fatal("%s:%d, Cannot find device \"%s\"\n", ERROR_POS, nhop[i].dev); if (nhop[i].hops == 0) { debug(DBG_NORMAL, "hops=%d is invalid. Using hops=255\n", nhop[i].hops); rtnh->rtnh_hops = 255; } else rtnh->rtnh_hops = nhop[i].hops - 1; rtnh = RTNH_NEXT(rtnh); i++; } if (rta->rta_len > RTA_LENGTH(0)) addattr_l(n, 1024, RTA_MULTIPATH, RTA_DATA(rta), RTA_PAYLOAD(rta)); return 0; } /* * route_exec: replaces, adds or deletes a route from the routing table. * `to' and nhops->gw must be addresses given in network order */ int route_exec(int route_cmd, int route_type, int route_scope, unsigned flags, inet_prefix * src, inet_prefix * to, struct nexthop *nhops, char *dev, u_char table) { struct rt_request req; struct rtnl_handle rth; setzero(&req, sizeof(req)); if (!table) table = RT_TABLE_MAIN; req.nh.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); req.nh.nlmsg_flags = NLM_F_REQUEST | flags; req.nh.nlmsg_type = route_cmd; req.rt.rtm_family = AF_UNSPEC; req.rt.rtm_table = table; req.rt.rtm_protocol = RTPROT_NETSUKUKU; req.rt.rtm_scope = RT_SCOPE_NOWHERE; req.rt.rtm_type = RTN_UNSPEC; /* kernel protocol layer */ if (table == RT_TABLE_LOCAL) req.rt.rtm_protocol = RTPROT_KERNEL; if (route_cmd != RTM_DELROUTE) { req.rt.rtm_scope = RT_SCOPE_UNIVERSE; req.rt.rtm_type = RTN_UNICAST; } if (route_type) req.rt.rtm_type = route_type; if (route_scope) req.rt.rtm_scope = route_scope; else if (req.rt.rtm_type == RTN_LOCAL) req.rt.rtm_scope = RT_SCOPE_HOST; if (rtnl_open(&rth, 0) < 0) return -1; if (dev || nhops) ll_init_map(&rth); #ifdef LINUX_2_6_14 uint32_t mp_alg = NTK_MULTIPATH_ALGO; addattr_l(&req.n, sizeof(req), RTA_MP_ALGO, &mp_alg, sizeof(mp_alg)); #endif if (dev) { int idx; if ((idx = ll_name_to_index(dev)) == 0) { error("%s:%d, Device \"%s\" doesn't really exist\n", ERROR_POS, dev); return -1; } addattr32(&req.nh, sizeof(req), RTA_OIF, idx); } if (to) { req.rt.rtm_family = to->family; req.rt.rtm_dst_len = to->bits; if (!to->data[0] && !to->data[1] && !to->data[2] && !to->data[3]) { /* Modify the default gw */ if (route_cmd == RTM_DELROUTE) req.rt.rtm_protocol = 0; } if (to->len) addattr_l(&req.nh, sizeof(req), RTA_DST, &to->data, to->len); } if (src) { if (req.rt.rtm_family == AF_UNSPEC) req.rt.rtm_family = src->family; addattr_l(&req.nh, sizeof(req), RTA_PREFSRC, &src->data, src->len); } if (nhops) add_nexthops(&req.nh, &req.rt, nhops); if (req.rt.rtm_family == AF_UNSPEC) req.rt.rtm_family = AF_INET; /*Finaly stage: <> */ if (rtnl_talk(&rth, &req.nh, 0, 0, NULL, NULL, NULL) < 0) return -1; rtnl_close(&rth); return 0; } /* * route_get_gw: if the route stored in `who' and `n' is matched by the * `filter', it stores the gateway address of that route in `arg', which * is a pointer to an inet_prefix struct. The address is stored in host order. * The dev name of the route is appended at `arg'+sizeof(inet_prefix). * Only the non-deleted routes are considered. */ int route_get_gw(const struct sockaddr_nl *who, struct nlmsghdr *n, void *arg) { struct rtmsg *r = NLMSG_DATA(n); int len = n->nlmsg_len; struct rtattr *tb[RTA_MAX + 1]; inet_prefix dst; inet_prefix via; int host_len = -1; if (n->nlmsg_type != RTM_NEWROUTE && n->nlmsg_type != RTM_DELROUTE) return 0; if (filter.flushb && n->nlmsg_type != RTM_NEWROUTE) return 0; len -= NLMSG_LENGTH(sizeof(*r)); if (len < 0) return -1; if (r->rtm_family == AF_INET6) host_len = 128; else if (r->rtm_family == AF_INET) host_len = 32; else if (r->rtm_family == AF_DECnet) host_len = 16; else if (r->rtm_family == AF_IPX) host_len = 80; if (r->rtm_family == AF_INET6) { if (filter.tb) { if (filter.tb < 0) { if (!(r->rtm_flags & RTM_F_CLONED)) return 0; } else { if (r->rtm_flags & RTM_F_CLONED) return 0; if (filter.tb == RT_TABLE_LOCAL) { if (r->rtm_type != RTN_LOCAL) return 0; } else if (filter.tb == RT_TABLE_MAIN) { if (r->rtm_type == RTN_LOCAL) return 0; } else { return 0; } } } } else { if (filter.tb > 0 && filter.tb != r->rtm_table) return 0; } if ((filter.protocol ^ r->rtm_protocol) & filter.protocolmask) return 0; if ((filter.scope ^ r->rtm_scope) & filter.scopemask) return 0; if ((filter.type ^ r->rtm_type) & filter.typemask) return 0; if ((filter.tos ^ r->rtm_tos) & filter.tosmask) return 0; if (filter.rdst.family && (r->rtm_family != filter.rdst.family || filter.rdst.bits > r->rtm_dst_len)) return 0; if (filter.mdst.family && (r->rtm_family != filter.mdst.family || (filter.mdst.bits < r->rtm_dst_len))) return 0; if (filter.rsrc.family && (r->rtm_family != filter.rsrc.family || filter.rsrc.bits > r->rtm_src_len)) return 0; if (filter.msrc.family && (r->rtm_family != filter.msrc.family || (filter.msrc.bits < r->rtm_src_len))) return 0; if (filter.rvia.family && r->rtm_family != filter.rvia.family) return 0; if (filter.rprefsrc.family && r->rtm_family != filter.rprefsrc.family) return 0; parse_rtattr(tb, RTA_MAX, RTM_RTA(r), len); setzero(&dst, sizeof(dst)); dst.family = r->rtm_family; if (tb[RTA_DST]) { memcpy(&dst.data, RTA_DATA(tb[RTA_DST]), (r->rtm_dst_len + 7) / 8); } if (filter.rdst.family && inet_addr_match(&dst, &filter.rdst, filter.rdst.bits)) return 0; if (filter.mdst.family && inet_addr_match(&dst, &filter.mdst, r->rtm_dst_len)) return 0; if (n->nlmsg_type == RTM_DELROUTE) return 0; /* * ... and finally if all the tests passed, copy the gateway address */ if (tb[RTA_GATEWAY]) { memcpy(&via.data, RTA_DATA(tb[RTA_GATEWAY]), host_len / 8); via.family = r->rtm_family; inet_setip(arg, (u_int *) & via.data, via.family); } else if (tb[RTA_MULTIPATH]) { struct rtnexthop *nh = RTA_DATA(tb[RTA_MULTIPATH]); len = RTA_PAYLOAD(tb[RTA_MULTIPATH]); for (;;) { if (len < sizeof(*nh)) break; if (nh->rtnh_len > len) break; if (r->rtm_flags & RTM_F_CLONED && r->rtm_type == RTN_MULTICAST) goto skip_nexthop; if (nh->rtnh_len > sizeof(*nh)) { parse_rtattr(tb, RTA_MAX, RTNH_DATA(nh), nh->rtnh_len - sizeof(*nh)); if (tb[RTA_GATEWAY]) { memcpy(&via.data, RTA_DATA(tb[RTA_GATEWAY]), host_len / 8); via.family = r->rtm_family; inet_setip(arg, (u_int *) & via.data, via.family); /* Copy the interface name */ strncpy((char *) arg + sizeof(inet_prefix), ll_index_to_name(nh->rtnh_ifindex), IFNAMSIZ); break; } } skip_nexthop: len -= NLMSG_ALIGN(nh->rtnh_len); nh = RTNH_NEXT(nh); } } /* Copy the interface name */ if (tb[RTA_OIF] && filter.oifmask != -1) strncpy((char *) arg + sizeof(inet_prefix), ll_index_to_name(*(int *) RTA_DATA(tb[RTA_OIF])), IFNAMSIZ); return 0; } /* * route_get_exact_prefix: it dumps the routing table and search for a route * which has the prefix equal to `prefix', if it is found its destination * address is stored in `dst' and its interface name in `dev_name' (which must * be IFNAMSIZ big). */ int route_get_exact_prefix_dst(inet_prefix prefix, inet_prefix * dst, char *dev_name) { int do_ipv6 = AF_UNSPEC; struct rtnl_handle rth; char dst_data[sizeof(inet_prefix) + IFNAMSIZ]; route_reset_filter(); filter.tb = RT_TABLE_MAIN; filter.mdst = prefix; filter.rdst = filter.mdst; if (do_ipv6 == AF_UNSPEC && filter.tb) do_ipv6 = AF_INET; if (rtnl_open(&rth, 0) < 0) return -1; ll_init_map(&rth); if (rtnl_wilddump_request(&rth, do_ipv6, RTM_GETROUTE) < 0) { error(ERROR_MSG "Cannot send dump request" ERROR_POS); return -1; } setzero(dst_data, sizeof(dst_data)); if (rtnl_dump_filter(&rth, route_get_gw, dst_data, NULL, NULL) < 0) { debug(DBG_NORMAL, ERROR_MSG "Dump terminated" ERROR_POS); return -1; } inet_copy(dst, (inet_prefix *) dst_data); memcpy(dev_name, dst_data + sizeof(inet_prefix), IFNAMSIZ); rtnl_close(&rth); return 0; } int route_flush_cache(int family) { int len, err; int flush_fd; char ROUTE_FLUSH_SYSCTL[] = "/proc/sys/net/ipvX/route/flush"; char *buf = "-1"; len = strlen(buf); if (family == AF_INET) ROUTE_FLUSH_SYSCTL[17] = '4'; else if (family == AF_INET6) ROUTE_FLUSH_SYSCTL[17] = '6'; else return -1; flush_fd = open(ROUTE_FLUSH_SYSCTL, O_WRONLY); if (flush_fd < 0) { debug(DBG_NORMAL, "Cannot open \"%s\"\n", ROUTE_FLUSH_SYSCTL); return -1; } if ((err = write(flush_fd, (void *) buf, len)) == 0) { debug(DBG_NORMAL, "Warning: Route Cache not flushed\n"); return -1; } else if (err == -1) { debug(DBG_NORMAL, "Cannot flush routing cache: %s\n", strerror(errno)); return -1; } close(flush_fd); return 0; } int route_ip_forward(int family, int enable) { int len, err; int flush_fd; char *ROUTE_FORWARD_SYSCTL = "/proc/sys/net/ipv4/ip_forward"; char *ROUTE_FORWARD_SYSCTL_6 = "/proc/sys/net/ipv6/conf/all/forwarding"; char *sysctl_path, buf[2]; buf[0] = '1'; buf[1] = 0; len = strlen(buf); if (family == AF_INET) sysctl_path = ROUTE_FORWARD_SYSCTL; else if (family == AF_INET6) sysctl_path = ROUTE_FORWARD_SYSCTL_6; else return -1; if (!enable) buf[0] = '0'; flush_fd = open(sysctl_path, O_WRONLY); if (flush_fd < 0) { debug(DBG_NORMAL, "Cannot open \"%s\"\n", sysctl_path); return -1; } if ((err = write(flush_fd, (void *) buf, len)) == 0) { debug(DBG_NORMAL, "Warning: ip_forward setting changed\n"); return -1; } else if (err == -1) { debug(DBG_NORMAL, "Cannot change the ip_forward setting: %s\n", strerror(errno)); return -1; } close(flush_fd); return 0; } /* * route_rp_filter * * Modifies the /proc/sys/net/ipv4/conf/INTERFACE/rp_filter config file. */ int route_rp_filter(int family, char *dev, int enable) { int len, err, ret = 0; int flush_fd; /* The path is /proc/sys/net/ipv4/conf/INTERFACE/rp_filter */ const char *RP_FILTER_SYSCTL_1 = "/proc/sys/net/ipv4/conf/"; const char *RP_FILTER_SYSCTL_1_IPV6 = "/proc/sys/net/ipv6/conf/"; const char *RP_FILTER_SYSCTL_2 = "/rp_filter"; char *final_path = 0, buf[2]; buf[0] = '1'; buf[1] = 0; #define RP_FILTER_PATH_SZ (strlen(RP_FILTER_SYSCTL_1)+ \ strlen(RP_FILTER_SYSCTL_2)+IF_NAMESIZE+1) final_path = xzalloc(RP_FILTER_PATH_SZ); len = strlen(buf); if (family == AF_INET) { strcpy(final_path, RP_FILTER_SYSCTL_1); } else if (family == AF_INET6) { strcpy(final_path, RP_FILTER_SYSCTL_1_IPV6); } else ERROR_FINISH(ret, -1, finish); strcat(final_path, dev); strcat(final_path, RP_FILTER_SYSCTL_2); if (!enable) buf[0] = '0'; flush_fd = open(final_path, O_WRONLY); if (flush_fd < 0) { debug(DBG_NORMAL, "Cannot open \"%s\"\n", final_path); ERROR_FINISH(ret, -1, finish); } if ((err = write(flush_fd, (void *) buf, len)) == 0) { debug(DBG_NORMAL, "Warning: rp_filter setting changed\n"); ERROR_FINISH(ret, -1, finish); } else if (err == -1) { debug(DBG_NORMAL, "Cannot change the rp_filter setting: %s\n", strerror(errno)); ERROR_FINISH(ret, -1, finish); } close(flush_fd); finish: if (final_path) xfree(final_path); return ret; } /* * route_rp_filter_all_dev: do route_rp_filter() for all the interfaces * present in the `ifs' array. */ int route_rp_filter_all_dev(int family, interface * ifs, int ifs_n, int enable) { int i, ret = 0; for (i = 0; i < ifs_n; i++) ret += route_rp_filter(family, ifs[i].dev_name, enable); return ret; } /*Life is strange*/