toybox/toys/pending/route.c
<<
>>
Prefs
   1/* route.c - Display/edit network routing table.
   2 *
   3 * Copyright 2012 Ranjan Kumar <ranjankumar.bth@gmail.com>
   4 * Copyright 2013 Kyungwan Han <asura321@gmail.com>
   5 * Copyright 2020 Eric Molitor <eric@molitor.org>
   6 *
   7 * No Standard
   8 *
   9 * route add -net target 10.0.0.0 netmask 255.0.0.0 dev eth0
  10 * route del delete
  11 * delete net route, must match netmask, informative error message
  12 *
  13 * mod dyn reinstate metric netmask gw mss window irtt dev
  14
  15USE_ROUTE(NEWTOY(route, "?neA:", TOYFLAG_SBIN))
  16config ROUTE
  17  bool "route"
  18  default n
  19  help
  20    usage: route [-ne] [-A [inet|inet6]] [add|del TARGET [OPTIONS]]
  21
  22    Display, add or delete network routes in the "Forwarding Information Base",
  23    which send packets out a network interface to an address.
  24
  25    -n  Show numerical addresses (no DNS lookups)
  26    -e  display netstat fields
  27
  28    Assigning an address to an interface automatically creates an appropriate
  29    network route ("ifconfig eth0 10.0.2.15/8" does "route add 10.0.0.0/8 eth0"
  30    for you), although some devices (such as loopback) won't show it in the
  31    table. For machines more than one hop away, you need to specify a gateway
  32    (ala "route add default gw 10.0.2.2").
  33
  34    The address "default" is a wildcard address (0.0.0.0/0) matching all
  35    packets without a more specific route.
  36
  37    Available OPTIONS include:
  38    reject   - blocking route (force match failure)
  39    dev NAME - force matching packets out this interface (ala "eth0")
  40    netmask  - old way of saying things like ADDR/24
  41    gw ADDR  - forward packets to gateway ADDR
  42*/
  43
  44#define FOR_route
  45#include "toys.h"
  46#define _LINUX_SYSINFO_H     // workaround for musl bug
  47#include <linux/rtnetlink.h>
  48
  49GLOBALS(
  50  char *A;
  51)
  52
  53struct _arglist {
  54  char *arg;
  55  int action;
  56};
  57
  58static struct _arglist arglist1[] = {
  59  { "add", 1 }, { "del", 2 },
  60  { "delete", 2 }, { NULL, 0 }
  61};
  62
  63static struct _arglist arglist2[] = {
  64  { "-net", 1 }, { "-host", 2 },
  65  { NULL, 0 }
  66};
  67
  68void xsend(int sockfd, void *buf, size_t len)
  69{
  70  if (send(sockfd, buf, len, 0) != len) perror_exit("xsend");
  71}
  72
  73int xrecv(int sockfd, void *buf, size_t len)
  74{
  75  int msg_len = recv(sockfd, buf, len, 0);
  76  if (msg_len < 0) perror_exit("xrecv");
  77
  78  return msg_len;
  79}
  80
  81void addAttr(struct nlmsghdr *nl, int maxlen, void *attr, int type, int len)
  82{
  83  struct rtattr *rt;
  84  int rtlen = RTA_LENGTH(len);
  85  if (NLMSG_ALIGN(nl->nlmsg_len) + rtlen > maxlen) perror_exit("addAttr");
  86  rt = (struct rtattr*)((char *)nl + NLMSG_ALIGN(nl->nlmsg_len));
  87  rt->rta_type = type;
  88  rt->rta_len = rtlen;
  89  memcpy(RTA_DATA(rt), attr, len);
  90  nl->nlmsg_len = NLMSG_ALIGN(nl->nlmsg_len) + rtlen;
  91}
  92
  93static void get_hostname(sa_family_t f, void *a, char *dst, size_t len) {
  94  size_t a_len = (AF_INET6 == f) ? sizeof(struct in6_addr) : sizeof(struct in_addr);
  95
  96  struct hostent *host = gethostbyaddr(a, a_len, f);
  97  if (host) xstrncpy(dst, host->h_name, len);
  98}
  99
 100static void display_routes(sa_family_t f)
 101{
 102  int fd, msg_hdr_len, route_protocol;
 103  struct {
 104    struct nlmsghdr nl;
 105    struct rtmsg rt;
 106  } req;
 107  struct nlmsghdr buf[8192 / sizeof(struct nlmsghdr)];
 108  struct nlmsghdr *msg_hdr_ptr;
 109  struct rtmsg *route_entry;
 110  struct rtattr *rteattr;
 111
 112  fd = xsocket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
 113
 114  memset(&req, 0, sizeof(req));
 115  req.nl.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
 116  req.nl.nlmsg_type = RTM_GETROUTE;
 117  req.nl.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
 118  req.nl.nlmsg_pid = getpid();
 119  req.nl.nlmsg_seq = 1;
 120  req.rt.rtm_family = f;
 121  req.rt.rtm_table = RT_TABLE_MAIN;
 122  xsend(fd, &req, sizeof(req));
 123
 124  if (f == AF_INET) {
 125    xprintf("Kernel IP routing table\n"
 126            "Destination     Gateway         Genmask         Flags %s Iface\n",
 127            FLAG(e) ? "  MSS Window  irtt" : "Metric Ref    Use");
 128  } else {
 129    xprintf("Kernel IPv6 routing table\n"
 130            "%-31s%-26s Flag Metric Ref Use If\n", "Destination", "Next Hop");
 131  }
 132
 133  msg_hdr_len = xrecv(fd, buf, sizeof(buf));
 134  msg_hdr_ptr = buf;
 135  while (msg_hdr_ptr->nlmsg_type != NLMSG_DONE) {
 136    while (NLMSG_OK(msg_hdr_ptr, msg_hdr_len)) {
 137      route_entry = NLMSG_DATA(msg_hdr_ptr);
 138      route_protocol = route_entry->rtm_protocol;
 139
 140      // Annoyingly NLM_F_MATCH is not yet implemented so even if we pass in
 141      // RT_TABLE_MAIN with RTM_GETROUTE it still returns everything so we
 142      // have to filter here.
 143      if (route_entry->rtm_table == RT_TABLE_MAIN) {
 144        int route_attribute_len;
 145        char dest[INET6_ADDRSTRLEN], gate[INET6_ADDRSTRLEN], netmask[32],
 146             flags[10] = "U", if_name[IF_NAMESIZE] = "-";
 147        unsigned priority = 0, mss = 0, win = 0, irtt = 0, ref = 0, use = 0,
 148                 route_netmask, metric_len;
 149        struct in_addr netmask_addr;
 150        struct rtattr *metric;
 151        struct rta_cacheinfo *cache_info;
 152
 153        if (f == AF_INET) {
 154          strcpy(dest, FLAG(n) ? "0.0.0.0" : "default");
 155          strcpy(gate, FLAG(n) ? "*" : "0.0.0.0");
 156          strcpy(netmask, "0.0.0.0");
 157        } else {
 158          strcpy(dest, "::");
 159          strcpy(gate, "::");
 160        }
 161
 162        route_netmask = route_entry->rtm_dst_len;
 163        if (route_netmask == 0) netmask_addr.s_addr = ~((in_addr_t) -1);
 164        else netmask_addr.s_addr = htonl(~((1 << (32 - route_netmask)) - 1));
 165        inet_ntop(AF_INET, &netmask_addr, netmask, sizeof(netmask));
 166
 167        rteattr = RTM_RTA(route_entry);
 168        route_attribute_len = RTM_PAYLOAD(msg_hdr_ptr);
 169        while (RTA_OK(rteattr, route_attribute_len)) {
 170          switch (rteattr->rta_type) {
 171            case RTA_DST:
 172              if (FLAG(n)) inet_ntop(f, RTA_DATA(rteattr), dest, sizeof(dest));
 173              else get_hostname(f, RTA_DATA(rteattr), dest, sizeof(dest));
 174              break;
 175
 176            case RTA_GATEWAY:
 177              if (FLAG(n)) inet_ntop(f, RTA_DATA(rteattr), gate, sizeof(dest));
 178              else get_hostname(f, RTA_DATA(rteattr), gate, sizeof(dest));
 179              strcat(flags, "G");
 180              break;
 181
 182            case RTA_PRIORITY:
 183              priority = *(unsigned *)RTA_DATA(rteattr);
 184              break;
 185
 186            case RTA_OIF:
 187              if_indextoname(*(int *)RTA_DATA(rteattr), if_name);
 188              break;
 189
 190            case RTA_METRICS:
 191              metric_len = RTA_PAYLOAD(rteattr);
 192              for (metric = RTA_DATA(rteattr); RTA_OK(metric, metric_len);
 193                   metric = RTA_NEXT(metric, metric_len))
 194                if (metric->rta_type == RTAX_ADVMSS) 
 195                  mss = *(unsigned *)RTA_DATA(metric);
 196                else if (metric->rta_type == RTAX_WINDOW)
 197                  win = *(unsigned *)RTA_DATA(metric);
 198                else if (metric->rta_type == RTAX_RTT)
 199                  irtt = (*(unsigned *)RTA_DATA(metric))/8;
 200              break;
 201
 202            case RTA_CACHEINFO:
 203              cache_info = RTA_DATA(rteattr);
 204              ref = cache_info->rta_clntref;
 205              use = cache_info->rta_used;
 206              break;
 207          }
 208
 209          rteattr = RTA_NEXT(rteattr, route_attribute_len);
 210        }
 211
 212        if (route_entry->rtm_type == RTN_UNREACHABLE) flags[0] = '!';
 213        if (route_netmask == 32) strcat(flags, "H");
 214        if (route_protocol == RTPROT_REDIRECT) strcat(flags, "D");
 215
 216        if (f == AF_INET) {
 217          xprintf("%-15.15s %-15.15s %-16s%-6s", dest, gate, netmask, flags);
 218          if (FLAG(e)) xprintf("%5d %-5d %6d %s\n", mss, win, irtt, if_name);
 219          else xprintf("%-6d %-2d %7d %s\n", priority, ref, use, if_name);
 220        } else {
 221          char *dest_with_mask = xmprintf("%s/%u", dest, route_netmask);
 222          xprintf("%-30s %-26s %-4s %-6d %-4d %2d %-8s\n",
 223                  dest_with_mask, gate, flags, priority, ref, use, if_name);
 224          free(dest_with_mask);
 225        }
 226      }
 227      msg_hdr_ptr = NLMSG_NEXT(msg_hdr_ptr, msg_hdr_len);
 228    }
 229
 230    msg_hdr_len = xrecv(fd, buf, sizeof(buf));
 231    msg_hdr_ptr = buf;
 232  }
 233
 234  xclose(fd);
 235}
 236
 237// find parameter (add/del/net/host) in list, return appropriate action or 0.
 238static int get_action(char ***argv, struct _arglist *list)
 239{
 240  struct _arglist *alist;
 241
 242  if (!**argv) return 0;
 243  for (alist = list; alist->arg; alist++) { //find the given parameter in list
 244    if (!strcmp(**argv, alist->arg)) {
 245      *argv += 1;
 246      return alist->action;
 247    }
 248  }
 249  return 0;
 250}
 251
 252// add/del a route.
 253static void setroute(sa_family_t f, char **argv)
 254{
 255  char *tgtip;
 256  int sockfd, arg2_action;
 257  int action = get_action(&argv, arglist1); //verify the arg for add/del.
 258  struct nlmsghdr buf[8192 / sizeof(struct nlmsghdr)];
 259  struct nlmsghdr *nlMsg;
 260  struct rtmsg *rtMsg;
 261
 262  if (!action || !*argv) help_exit("setroute");
 263  arg2_action = get_action(&argv, arglist2); //verify the arg for -net or -host
 264  if (!*argv) help_exit("setroute");
 265  tgtip = *argv++;
 266  sockfd = xsocket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
 267  memset(buf, 0, sizeof(buf));
 268  nlMsg = (struct nlmsghdr *) buf;
 269  rtMsg = (struct rtmsg *) NLMSG_DATA(nlMsg);
 270
 271  nlMsg->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
 272
 273  //TODO(emolitor): Improve action and arg2_action handling
 274  if (action == 1) { // Add
 275    nlMsg->nlmsg_type = RTM_NEWROUTE;
 276    nlMsg->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL;
 277  } else { // Delete
 278    nlMsg->nlmsg_type = RTM_DELROUTE;
 279    nlMsg->nlmsg_flags = NLM_F_REQUEST;
 280  }
 281
 282  nlMsg->nlmsg_pid = getpid();
 283  nlMsg->nlmsg_seq = 1;
 284  rtMsg->rtm_family = f;
 285  rtMsg->rtm_table = RT_TABLE_UNSPEC;
 286  rtMsg->rtm_type = RTN_UNICAST;
 287  rtMsg->rtm_protocol = RTPROT_UNSPEC;
 288  rtMsg->rtm_flags = RTM_F_NOTIFY;
 289  rtMsg->rtm_dst_len = rtMsg->rtm_src_len = (f == AF_INET) ? 32 : 128;
 290
 291  if (arg2_action == 2) rtMsg->rtm_scope = RT_SCOPE_HOST;
 292
 293  size_t addr_len = sizeof(struct in_addr);
 294  if (f == AF_INET6) addr_len = sizeof(struct in6_addr);
 295  unsigned char addr[sizeof(struct in6_addr)] = {0,};
 296
 297  for (; *argv; argv++) {
 298    if (!strcmp(*argv, "mod")) continue;
 299    else if (!strcmp(*argv, "dyn")) continue;
 300    else if (!strcmp(*argv, "reinstate")) continue;
 301    else if (!strcmp(*argv, "reject")) rtMsg->rtm_type = RTN_UNREACHABLE;
 302    else {
 303      if (!argv[1]) show_help(stdout, 1);
 304
 305      if (!strcmp(*argv, "metric")) {
 306        unsigned int priority = atolx_range(argv[1], 0, UINT_MAX);
 307        addAttr(nlMsg, sizeof(toybuf), &priority, RTA_PRIORITY, sizeof(unsigned int));
 308      } else if (!strcmp(*argv, "netmask")) {
 309        uint32_t netmask;
 310        char *ptr;
 311        uint32_t naddr[4] = {0,};
 312        uint64_t plen;
 313
 314        netmask = (f == AF_INET6) ? 128 : 32; // set default netmask
 315        plen = strtoul(argv[1], &ptr, 0);
 316
 317        if (!ptr || ptr == argv[1] || *ptr || !plen || plen > netmask) {
 318          if (!inet_pton(f, argv[1], &naddr)) error_exit("invalid netmask");
 319          if (f == AF_INET) {
 320            uint32_t mask = htonl(*naddr), host = ~mask;
 321            if (host & (host + 1)) error_exit("invalid netmask");
 322            for (plen = 0; mask; mask <<= 1) ++plen;
 323            if (plen > 32) error_exit("invalid netmask");
 324          }
 325        }
 326        netmask = plen;
 327        rtMsg->rtm_dst_len = netmask;
 328      } else if (!strcmp(*argv, "gw")) {
 329        if (!inet_pton(f, argv[1], &addr)) error_exit("invalid gw");
 330        addAttr(nlMsg, sizeof(toybuf), &addr, RTA_GATEWAY, addr_len);
 331      } else if (!strcmp(*argv, "mss")) {
 332        // TODO(emolitor): Add RTA_METRICS support
 333        //set the TCP Maximum Segment Size for connections over this route.
 334        //rt->rt_mtu = atolx_range(argv[1], 64, 65536);
 335        //rt->rt_flags |= RTF_MSS;
 336      } else if (!strcmp(*argv, "window")) {
 337        // TODO(emolitor): Add RTA_METRICS support
 338        //set the TCP window size for connections over this route to W bytes.
 339        //rt->rt_window = atolx_range(argv[1], 128, INT_MAX); //win low
 340        //rt->rt_flags |= RTF_WINDOW;
 341      } else if (!strcmp(*argv, "irtt")) {
 342        // TODO(emolitor): Add RTA_METRICS support
 343        //rt->rt_irtt = atolx_range(argv[1], 0, INT_MAX);
 344        //rt->rt_flags |= RTF_IRTT;
 345      } else if (!strcmp(*argv, "dev")) {
 346        unsigned int if_idx = if_nametoindex(argv[1]);
 347        if (!if_idx) perror_exit("dev");
 348        addAttr(nlMsg, sizeof(toybuf), &if_idx, RTA_OIF, sizeof(unsigned int));
 349      } else help_exit("no '%s'", *argv);
 350      argv++;
 351    }
 352  }
 353
 354  if (strcmp(tgtip, "default") != 0) {
 355    char *prefix = strtok(0, "/");
 356
 357    if (prefix) rtMsg->rtm_dst_len = strtoul(prefix, &prefix, 0);
 358    if (!inet_pton(f, strtok(tgtip, "/"), &addr)) error_exit("invalid target");
 359    addAttr(nlMsg, sizeof(toybuf), &addr, RTA_DST, addr_len);
 360  } else rtMsg->rtm_dst_len = 0;
 361
 362  xsend(sockfd, nlMsg, nlMsg->nlmsg_len);
 363  xclose(sockfd);
 364}
 365
 366void route_main(void)
 367{
 368  if (!*toys.optargs) {
 369    if (!TT.A || !strcmp(TT.A, "inet")) display_routes(AF_INET);
 370    else if (!strcmp(TT.A, "inet6")) display_routes(AF_INET6);
 371    else show_help(stdout, 1);
 372  } else {
 373    if (!TT.A) {
 374      if (toys.optc>1 && strchr(toys.optargs[1], ':')) {
 375          xprintf("WARNING: Implicit IPV6 address using -Ainet6\n");
 376          TT.A = "inet6";
 377      } else TT.A = "inet";
 378    }
 379
 380    if (!strcmp(TT.A, "inet")) setroute(AF_INET, toys.optargs);
 381    else setroute(AF_INET6, toys.optargs);
 382  }
 383}
 384