• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
15 USE_ROUTE(NEWTOY(route, "?neA:", TOYFLAG_SBIN))
16 config 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 
49 GLOBALS(
50   char *A;
51 )
52 
53 struct _arglist {
54   char *arg;
55   int action;
56 };
57 
58 static struct _arglist arglist1[] = {
59   { "add", 1 }, { "del", 2 },
60   { "delete", 2 }, { NULL, 0 }
61 };
62 
63 static struct _arglist arglist2[] = {
64   { "-net", 1 }, { "-host", 2 },
65   { NULL, 0 }
66 };
67 
xsend(int sockfd,void * buf,size_t len)68 void xsend(int sockfd, void *buf, size_t len)
69 {
70   if (send(sockfd, buf, len, 0) != len) perror_exit("xsend");
71 }
72 
xrecv(int sockfd,void * buf,size_t len)73 int 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 
addAttr(struct nlmsghdr * nl,int maxlen,void * attr,int type,int len)81 void 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 
get_hostname(sa_family_t f,void * a,char * dst,size_t len)93 static 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 
display_routes(sa_family_t f)100 static 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.
get_action(char *** argv,struct _arglist * list)238 static 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.
setroute(sa_family_t f,char ** argv)253 static 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 
route_main(void)366 void 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