/* route.c - Display/edit network routing table. * * Copyright 2012 Ranjan Kumar * Copyright 2013 Kyungwan Han * Copyright 2020 Eric Molitor * * No Standard * * route add -net target 10.0.0.0 netmask 255.0.0.0 dev eth0 * route del delete * delete net route, must match netmask, informative error message * * mod dyn reinstate metric netmask gw mss window irtt dev USE_ROUTE(NEWTOY(route, "?neA:", TOYFLAG_SBIN)) config ROUTE bool "route" default n help usage: route [-ne] [-A [inet|inet6]] [add|del TARGET [OPTIONS]] Display, add or delete network routes in the "Forwarding Information Base", which send packets out a network interface to an address. -n Show numerical addresses (no DNS lookups) -e display netstat fields Assigning an address to an interface automatically creates an appropriate network route ("ifconfig eth0 10.0.2.15/8" does "route add 10.0.0.0/8 eth0" for you), although some devices (such as loopback) won't show it in the table. For machines more than one hop away, you need to specify a gateway (ala "route add default gw 10.0.2.2"). The address "default" is a wildcard address (0.0.0.0/0) matching all packets without a more specific route. Available OPTIONS include: reject - blocking route (force match failure) dev NAME - force matching packets out this interface (ala "eth0") netmask - old way of saying things like ADDR/24 gw ADDR - forward packets to gateway ADDR */ #define FOR_route #include "toys.h" #define _LINUX_SYSINFO_H // workaround for musl bug #include GLOBALS( char *A; ) struct _arglist { char *arg; int action; }; static struct _arglist arglist1[] = { { "add", 1 }, { "del", 2 }, { "delete", 2 }, { NULL, 0 } }; static struct _arglist arglist2[] = { { "-net", 1 }, { "-host", 2 }, { NULL, 0 } }; void xsend(int sockfd, void *buf, size_t len) { if (send(sockfd, buf, len, 0) != len) perror_exit("xsend"); } int xrecv(int sockfd, void *buf, size_t len) { int msg_len = recv(sockfd, buf, len, 0); if (msg_len < 0) perror_exit("xrecv"); return msg_len; } void addAttr(struct nlmsghdr *nl, int maxlen, void *attr, int type, int len) { struct rtattr *rt; int rtlen = RTA_LENGTH(len); if (NLMSG_ALIGN(nl->nlmsg_len) + rtlen > maxlen) perror_exit("addAttr"); rt = (struct rtattr*)((char *)nl + NLMSG_ALIGN(nl->nlmsg_len)); rt->rta_type = type; rt->rta_len = rtlen; memcpy(RTA_DATA(rt), attr, len); nl->nlmsg_len = NLMSG_ALIGN(nl->nlmsg_len) + rtlen; } static void get_hostname(sa_family_t f, void *a, char *dst, size_t len) { size_t a_len = (AF_INET6 == f) ? sizeof(struct in6_addr) : sizeof(struct in_addr); struct hostent *host = gethostbyaddr(a, a_len, f); if (host) xstrncpy(dst, host->h_name, len); } static void display_routes(sa_family_t f) { int fd, msg_hdr_len, route_protocol; struct { struct nlmsghdr nl; struct rtmsg rt; } req; struct nlmsghdr buf[8192 / sizeof(struct nlmsghdr)]; struct nlmsghdr *msg_hdr_ptr; struct rtmsg *route_entry; struct rtattr *rteattr; fd = xsocket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); memset(&req, 0, sizeof(req)); req.nl.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); req.nl.nlmsg_type = RTM_GETROUTE; req.nl.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; req.nl.nlmsg_pid = getpid(); req.nl.nlmsg_seq = 1; req.rt.rtm_family = f; req.rt.rtm_table = RT_TABLE_MAIN; xsend(fd, &req, sizeof(req)); if (f == AF_INET) { xprintf("Kernel IP routing table\n" "Destination Gateway Genmask Flags %s Iface\n", FLAG(e) ? " MSS Window irtt" : "Metric Ref Use"); } else { xprintf("Kernel IPv6 routing table\n" "%-31s%-26s Flag Metric Ref Use If\n", "Destination", "Next Hop"); } msg_hdr_len = xrecv(fd, buf, sizeof(buf)); msg_hdr_ptr = buf; while (msg_hdr_ptr->nlmsg_type != NLMSG_DONE) { while (NLMSG_OK(msg_hdr_ptr, msg_hdr_len)) { route_entry = NLMSG_DATA(msg_hdr_ptr); route_protocol = route_entry->rtm_protocol; // Annoyingly NLM_F_MATCH is not yet implemented so even if we pass in // RT_TABLE_MAIN with RTM_GETROUTE it still returns everything so we // have to filter here. if (route_entry->rtm_table == RT_TABLE_MAIN) { int route_attribute_len; char dest[INET6_ADDRSTRLEN], gate[INET6_ADDRSTRLEN], netmask[32], flags[10] = "U", if_name[IF_NAMESIZE] = "-"; unsigned priority = 0, mss = 0, win = 0, irtt = 0, ref = 0, use = 0, route_netmask, metric_len; struct in_addr netmask_addr; struct rtattr *metric; struct rta_cacheinfo *cache_info; if (f == AF_INET) { strcpy(dest, FLAG(n) ? "0.0.0.0" : "default"); strcpy(gate, FLAG(n) ? "*" : "0.0.0.0"); strcpy(netmask, "0.0.0.0"); } else { strcpy(dest, "::"); strcpy(gate, "::"); } route_netmask = route_entry->rtm_dst_len; if (route_netmask == 0) netmask_addr.s_addr = ~((in_addr_t) -1); else netmask_addr.s_addr = htonl(~((1 << (32 - route_netmask)) - 1)); inet_ntop(AF_INET, &netmask_addr, netmask, sizeof(netmask)); rteattr = RTM_RTA(route_entry); route_attribute_len = RTM_PAYLOAD(msg_hdr_ptr); while (RTA_OK(rteattr, route_attribute_len)) { switch (rteattr->rta_type) { case RTA_DST: if (FLAG(n)) inet_ntop(f, RTA_DATA(rteattr), dest, sizeof(dest)); else get_hostname(f, RTA_DATA(rteattr), dest, sizeof(dest)); break; case RTA_GATEWAY: if (FLAG(n)) inet_ntop(f, RTA_DATA(rteattr), gate, sizeof(dest)); else get_hostname(f, RTA_DATA(rteattr), gate, sizeof(dest)); strcat(flags, "G"); break; case RTA_PRIORITY: priority = *(unsigned *)RTA_DATA(rteattr); break; case RTA_OIF: if_indextoname(*(int *)RTA_DATA(rteattr), if_name); break; case RTA_METRICS: metric_len = RTA_PAYLOAD(rteattr); for (metric = RTA_DATA(rteattr); RTA_OK(metric, metric_len); metric = RTA_NEXT(metric, metric_len)) if (metric->rta_type == RTAX_ADVMSS) mss = *(unsigned *)RTA_DATA(metric); else if (metric->rta_type == RTAX_WINDOW) win = *(unsigned *)RTA_DATA(metric); else if (metric->rta_type == RTAX_RTT) irtt = (*(unsigned *)RTA_DATA(metric))/8; break; case RTA_CACHEINFO: cache_info = RTA_DATA(rteattr); ref = cache_info->rta_clntref; use = cache_info->rta_used; break; } rteattr = RTA_NEXT(rteattr, route_attribute_len); } if (route_entry->rtm_type == RTN_UNREACHABLE) flags[0] = '!'; if (route_netmask == 32) strcat(flags, "H"); if (route_protocol == RTPROT_REDIRECT) strcat(flags, "D"); if (f == AF_INET) { xprintf("%-15.15s %-15.15s %-16s%-6s", dest, gate, netmask, flags); if (FLAG(e)) xprintf("%5d %-5d %6d %s\n", mss, win, irtt, if_name); else xprintf("%-6d %-2d %7d %s\n", priority, ref, use, if_name); } else { char *dest_with_mask = xmprintf("%s/%u", dest, route_netmask); xprintf("%-30s %-26s %-4s %-6d %-4d %2d %-8s\n", dest_with_mask, gate, flags, priority, ref, use, if_name); free(dest_with_mask); } } msg_hdr_ptr = NLMSG_NEXT(msg_hdr_ptr, msg_hdr_len); } msg_hdr_len = xrecv(fd, buf, sizeof(buf)); msg_hdr_ptr = buf; } xclose(fd); } // find parameter (add/del/net/host) in list, return appropriate action or 0. static int get_action(char ***argv, struct _arglist *list) { struct _arglist *alist; if (!**argv) return 0; for (alist = list; alist->arg; alist++) { //find the given parameter in list if (!strcmp(**argv, alist->arg)) { *argv += 1; return alist->action; } } return 0; } // add/del a route. static void setroute(sa_family_t f, char **argv) { char *tgtip; int sockfd, arg2_action; int action = get_action(&argv, arglist1); //verify the arg for add/del. struct nlmsghdr buf[8192 / sizeof(struct nlmsghdr)]; struct nlmsghdr *nlMsg; struct rtmsg *rtMsg; if (!action || !*argv) help_exit("setroute"); arg2_action = get_action(&argv, arglist2); //verify the arg for -net or -host if (!*argv) help_exit("setroute"); tgtip = *argv++; sockfd = xsocket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); memset(buf, 0, sizeof(buf)); nlMsg = (struct nlmsghdr *) buf; rtMsg = (struct rtmsg *) NLMSG_DATA(nlMsg); nlMsg->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); //TODO(emolitor): Improve action and arg2_action handling if (action == 1) { // Add nlMsg->nlmsg_type = RTM_NEWROUTE; nlMsg->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL; } else { // Delete nlMsg->nlmsg_type = RTM_DELROUTE; nlMsg->nlmsg_flags = NLM_F_REQUEST; } nlMsg->nlmsg_pid = getpid(); nlMsg->nlmsg_seq = 1; rtMsg->rtm_family = f; rtMsg->rtm_table = RT_TABLE_UNSPEC; rtMsg->rtm_type = RTN_UNICAST; rtMsg->rtm_protocol = RTPROT_UNSPEC; rtMsg->rtm_flags = RTM_F_NOTIFY; rtMsg->rtm_dst_len = rtMsg->rtm_src_len = (f == AF_INET) ? 32 : 128; if (arg2_action == 2) rtMsg->rtm_scope = RT_SCOPE_HOST; size_t addr_len = sizeof(struct in_addr); if (f == AF_INET6) addr_len = sizeof(struct in6_addr); unsigned char addr[sizeof(struct in6_addr)] = {0,}; for (; *argv; argv++) { if (!strcmp(*argv, "mod")) continue; else if (!strcmp(*argv, "dyn")) continue; else if (!strcmp(*argv, "reinstate")) continue; else if (!strcmp(*argv, "reject")) rtMsg->rtm_type = RTN_UNREACHABLE; else { if (!argv[1]) show_help(stdout, 1); if (!strcmp(*argv, "metric")) { unsigned int priority = atolx_range(argv[1], 0, UINT_MAX); addAttr(nlMsg, sizeof(toybuf), &priority, RTA_PRIORITY, sizeof(unsigned int)); } else if (!strcmp(*argv, "netmask")) { uint32_t netmask; char *ptr; uint32_t naddr[4] = {0,}; uint64_t plen; netmask = (f == AF_INET6) ? 128 : 32; // set default netmask plen = strtoul(argv[1], &ptr, 0); if (!ptr || ptr == argv[1] || *ptr || !plen || plen > netmask) { if (!inet_pton(f, argv[1], &naddr)) error_exit("invalid netmask"); if (f == AF_INET) { uint32_t mask = htonl(*naddr), host = ~mask; if (host & (host + 1)) error_exit("invalid netmask"); for (plen = 0; mask; mask <<= 1) ++plen; if (plen > 32) error_exit("invalid netmask"); } } netmask = plen; rtMsg->rtm_dst_len = netmask; } else if (!strcmp(*argv, "gw")) { if (!inet_pton(f, argv[1], &addr)) error_exit("invalid gw"); addAttr(nlMsg, sizeof(toybuf), &addr, RTA_GATEWAY, addr_len); } else if (!strcmp(*argv, "mss")) { // TODO(emolitor): Add RTA_METRICS support //set the TCP Maximum Segment Size for connections over this route. //rt->rt_mtu = atolx_range(argv[1], 64, 65536); //rt->rt_flags |= RTF_MSS; } else if (!strcmp(*argv, "window")) { // TODO(emolitor): Add RTA_METRICS support //set the TCP window size for connections over this route to W bytes. //rt->rt_window = atolx_range(argv[1], 128, INT_MAX); //win low //rt->rt_flags |= RTF_WINDOW; } else if (!strcmp(*argv, "irtt")) { // TODO(emolitor): Add RTA_METRICS support //rt->rt_irtt = atolx_range(argv[1], 0, INT_MAX); //rt->rt_flags |= RTF_IRTT; } else if (!strcmp(*argv, "dev")) { unsigned int if_idx = if_nametoindex(argv[1]); if (!if_idx) perror_exit("dev"); addAttr(nlMsg, sizeof(toybuf), &if_idx, RTA_OIF, sizeof(unsigned int)); } else help_exit("no '%s'", *argv); argv++; } } if (strcmp(tgtip, "default") != 0) { char *prefix = strtok(0, "/"); if (prefix) rtMsg->rtm_dst_len = strtoul(prefix, &prefix, 0); if (!inet_pton(f, strtok(tgtip, "/"), &addr)) error_exit("invalid target"); addAttr(nlMsg, sizeof(toybuf), &addr, RTA_DST, addr_len); } else rtMsg->rtm_dst_len = 0; xsend(sockfd, nlMsg, nlMsg->nlmsg_len); xclose(sockfd); } void route_main(void) { if (!*toys.optargs) { if (!TT.A || !strcmp(TT.A, "inet")) display_routes(AF_INET); else if (!strcmp(TT.A, "inet6")) display_routes(AF_INET6); else show_help(stdout, 1); } else { if (!TT.A) { if (toys.optc>1 && strchr(toys.optargs[1], ':')) { xprintf("WARNING: Implicit IPV6 address using -Ainet6\n"); TT.A = "inet6"; } else TT.A = "inet"; } if (!strcmp(TT.A, "inet")) setroute(AF_INET, toys.optargs); else setroute(AF_INET6, toys.optargs); } }