• 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  * TODO: autodetect -net -host target dev -A (but complain)
10  * route add -net target 10.0.0.0 netmask 255.0.0.0 dev eth0
11  * route del delete
12  * delete net route, must match netmask, informative error message
13  *
14  * mod dyn reinstate metric netmask gw mss window irtt dev
15 
16 USE_ROUTE(NEWTOY(route, "?neA:", TOYFLAG_BIN))
17 config ROUTE
18   bool "route"
19   default n
20   help
21     usage: route [-ne] [-A [46]] [add|del TARGET [OPTIONS]]
22 
23     Display, add or delete network routes in the "Forwarding Information Base".
24 
25     -n	Show numerical addresses (no DNS lookups)
26     -e	display netstat fields
27 
28     Routing means sending packets out a network interface to an address.
29     The kernel can tell where to send packets one hop away by examining each
30     interface's address and netmask, so the most common use of this command
31     is to identify a "gateway" that forwards other traffic.
32 
33     Assigning an address to an interface automatically creates an appropriate
34     network route ("ifconfig eth0 10.0.2.15/8" does "route add 10.0.0.0/8 eth0"
35     for you), although some devices (such as loopback) won't show it in the
36     table. For machines more than one hop away, you need to specify a gateway
37     (ala "route add default gw 10.0.2.2").
38 
39     The address "default" is a wildcard address (0.0.0.0/0) matching all
40     packets without a more specific route.
41 
42     Available OPTIONS include:
43     reject   - blocking route (force match failure)
44     dev NAME - force matching packets out this interface (ala "eth0")
45     netmask  - old way of saying things like ADDR/24
46     gw ADDR  - forward packets to gateway ADDR
47 */
48 
49 #define FOR_route
50 #include "toys.h"
51 #include <net/route.h>
52 
53 GLOBALS(
54   char *family;
55 )
56 
57 #define DEFAULT_PREFIXLEN 128
58 #define INVALID_ADDR 0xffffffffUL
59 #define IPV6_ADDR_LEN 40 //32 + 7 (':') + 1 ('\0')
60 
61 struct _arglist {
62   char *arg;
63   int action;
64 };
65 
66 static struct _arglist arglist1[] = {
67   { "add", 1 }, { "del", 2 },
68   { "delete", 2 }, { NULL, 0 }
69 };
70 
71 static struct _arglist arglist2[] = {
72   { "-net", 1 }, { "-host", 2 },
73   { NULL, 0 }
74 };
75 
76 // to get the host name from the given ip.
get_hostname(char * ipstr,struct sockaddr_in * sockin)77 static int get_hostname(char *ipstr, struct sockaddr_in *sockin)
78 {
79   struct hostent *host;
80 
81   sockin->sin_family = AF_INET;
82   sockin->sin_port = 0;
83 
84   if (!strcmp(ipstr, "default")) {
85     sockin->sin_addr.s_addr = INADDR_ANY;
86     return 1;
87   }
88 
89   if (inet_aton(ipstr, &sockin->sin_addr)) return 0;
90   if (!(host = gethostbyname(ipstr))) perror_exit("resolving '%s'", ipstr);
91   memcpy(&sockin->sin_addr, host->h_addr_list[0], sizeof(struct in_addr));
92 
93   return 0;
94 }
95 
96 // used to extract the address info from the given ip.
get_addrinfo(char * ip,struct sockaddr_in6 * sock_in6)97 static int get_addrinfo(char *ip, struct sockaddr_in6 *sock_in6)
98 {
99   struct addrinfo hints, *result;
100   int status = 0;
101 
102   memset(&hints, 0, sizeof(struct addrinfo));
103   hints.ai_family = AF_INET6;
104   if ((status = getaddrinfo(ip, NULL, &hints, &result))) {
105     perror_msg("getaddrinfo: %s", gai_strerror(status));
106     return -1;
107   }
108   if (result) {
109     memcpy(sock_in6, result->ai_addr, sizeof(*sock_in6));
110     freeaddrinfo(result);
111   }
112   return 0;
113 }
114 
get_flag_value(char * str,int flags)115 static void get_flag_value(char *str, int flags)
116 {
117   // RTF_* bits in order:
118   // UP, GATEWAY, HOST, REINSTATE, DYNAMIC, MODIFIED, DEFAULT, ADDRCONF, CACHE
119   int i = 0, mask = 0x105003f;
120 
121   for (; mask; mask>>=1) if (mask&1) {
122     if (flags&(1<<i)) *str++ = "UGHRDMDAC"[i];
123     i++;
124   }
125   *str = 0;
126 }
127 
128 // extract inet4 route info from /proc/net/route file and display it.
display_routes(void)129 static void display_routes(void)
130 {
131   unsigned long dest, gate, mask;
132   int flags, ref, use, metric, mss, win, irtt, items;
133   char iface[64] = {0,}, flag_val[10]; //there are 9 flags "UGHRDMDAC" for route.
134 
135   FILE *fp = xfopen("/proc/net/route", "r");
136 
137   xprintf("Kernel IP routing table\n"
138       "Destination     Gateway         Genmask         Flags %s Iface\n",
139       (toys.optflags & FLAG_e)? "  MSS Window  irtt" : "Metric Ref    Use");
140 
141   if (fscanf(fp, "%*[^\n]\n") < 0) perror_exit("fscanf"); //skip 1st line
142   while ((items = fscanf(fp, "%63s%lx%lx%X%d%d%d%lx%d%d%d\n", iface, &dest,
143           &gate, &flags, &ref, &use, &metric, &mask, &mss, &win, &irtt)) == 11)
144   {
145     char *destip = toybuf, *gateip = toybuf+32, *maskip = toybuf+64; //ip string 16
146 
147     if (!(flags & RTF_UP)) continue; //skip down interfaces.
148 
149     if (!dest && !(toys.optflags & FLAG_n)) strcpy( destip, "default");
150     else if (!inet_ntop(AF_INET, &dest, destip, 32)) perror_exit("inet");
151 
152     if (!gate && !(toys.optflags & FLAG_n)) strcpy( gateip, "*");
153     else if (!inet_ntop(AF_INET, &gate, gateip, 32)) perror_exit("inet");
154 
155     if (!inet_ntop(AF_INET, &mask, maskip, 32)) perror_exit("inet");
156 
157     //Get flag Values
158     get_flag_value(flag_val, flags);
159     if (flags & RTF_REJECT) flag_val[0] = '!';
160     xprintf("%-15.15s %-15.15s %-16s%-6s", destip, gateip, maskip, flag_val);
161     if (toys.optflags & FLAG_e) xprintf("%5d %-5d %6d %s\n", mss, win, irtt, iface);
162     else xprintf("%-6d %-2d %7d %s\n", metric, ref, use, iface);
163   }
164 
165   if (items > 0 && feof(fp)) perror_exit("fscanf %d", items);
166   fclose(fp);
167 }
168 
169 /*
170  * find the given parameter in list like add/del/net/host.
171  * and if match found return the appropriate action.
172  */
get_action(char *** argv,struct _arglist * list)173 static int get_action(char ***argv, struct _arglist *list)
174 {
175   struct _arglist *alist;
176 
177   if (!**argv) return 0;
178   for (alist = list; alist->arg; alist++) { //find the given parameter in list
179     if (!strcmp(**argv, alist->arg)) {
180       *argv += 1;
181       return alist->action;
182     }
183   }
184   return 0;
185 }
186 
187 /*
188  * used to get the params like: metric, netmask, gw, mss, window, irtt, dev and their values.
189  * additionally set the flag values for reject, mod, dyn and reinstate.
190  */
get_next_params(char ** argv,struct rtentry * rt,char ** netmask)191 static void get_next_params(char **argv, struct rtentry *rt, char **netmask)
192 {
193   for (;*argv;argv++) {
194     if (!strcmp(*argv, "reject")) rt->rt_flags |= RTF_REJECT;
195     else if (!strcmp(*argv, "mod")) rt->rt_flags |= RTF_MODIFIED;
196     else if (!strcmp(*argv, "dyn")) rt->rt_flags |= RTF_DYNAMIC;
197     else if (!strcmp(*argv, "reinstate")) rt->rt_flags |= RTF_REINSTATE;
198     else {
199       if (!argv[1]) help_exit(0);
200 
201       //set the metric field in the routing table.
202       if (!strcmp(*argv, "metric"))
203         rt->rt_metric = atolx_range(argv[1], 0, ULONG_MAX) + 1;
204       else if (!strcmp(*argv, "netmask")) {
205         //when adding a network route, the netmask to be used.
206         struct sockaddr sock;
207         unsigned int addr_mask = (((struct sockaddr_in *)&((rt)->rt_genmask))->sin_addr.s_addr);
208 
209         if (addr_mask) help_exit("dup netmask");
210         *netmask = argv[1];
211         get_hostname(*netmask, (struct sockaddr_in *) &sock);
212         rt->rt_genmask = sock;
213       } else if (!strcmp(*argv, "gw")) {
214         //route packets via a gateway.
215         if (!(rt->rt_flags & RTF_GATEWAY)) {
216           if (!get_hostname(argv[1], (struct sockaddr_in *) &rt->rt_gateway))
217             rt->rt_flags |= RTF_GATEWAY;
218           else perror_exit("gateway '%s' is a NETWORK", argv[1]);
219         } else help_exit("dup gw");
220       } else if (!strcmp(*argv, "mss")) {
221         //set the TCP Maximum Segment Size for connections over this route.
222         rt->rt_mtu = atolx_range(argv[1], 64, 65536);
223         rt->rt_flags |= RTF_MSS;
224       } else if (!strcmp(*argv, "window")) {
225         //set the TCP window size for connections over this route to W bytes.
226         rt->rt_window = atolx_range(argv[1], 128, INT_MAX); //win low
227         rt->rt_flags |= RTF_WINDOW;
228       } else if (!strcmp(*argv, "irtt")) {
229         rt->rt_irtt = atolx_range(argv[1], 0, INT_MAX);
230         rt->rt_flags |= RTF_IRTT;
231       } else if (!strcmp(*argv, "dev") && !rt->rt_dev) rt->rt_dev = argv[1];
232       else help_exit("no '%s'", *argv);
233       argv++;
234     }
235   }
236 
237   if (!rt->rt_dev && (rt->rt_flags & RTF_REJECT)) rt->rt_dev = (char *)"lo";
238 }
239 
240 // verify the netmask and conflict in netmask and route address.
verify_netmask(struct rtentry * rt,char * netmask)241 static void verify_netmask(struct rtentry *rt, char *netmask)
242 {
243   unsigned int addr_mask = (((struct sockaddr_in *)&((rt)->rt_genmask))->sin_addr.s_addr);
244   unsigned int router_addr = ~(unsigned int)(((struct sockaddr_in *)&((rt)->rt_dst))->sin_addr.s_addr);
245 
246   if (addr_mask) {
247     addr_mask = ~ntohl(addr_mask);
248     if ((rt->rt_flags & RTF_HOST) && addr_mask != INVALID_ADDR)
249       perror_exit("conflicting netmask and host route");
250     if (addr_mask & (addr_mask + 1)) perror_exit("wrong netmask '%s'", netmask);
251     addr_mask = ((struct sockaddr_in *) &rt->rt_dst)->sin_addr.s_addr;
252     if (addr_mask & router_addr) perror_exit("conflicting netmask and route address");
253   }
254 }
255 
256 // add/del a route.
setroute(char ** argv)257 static void setroute(char **argv)
258 {
259   struct rtentry rt;
260   char *netmask, *targetip;
261   int is_net_or_host = 0, sokfd, arg2_action;
262   int action = get_action(&argv, arglist1); //verify the arg for add/del.
263 
264   if (!action || !*argv) help_exit("setroute");
265 
266   arg2_action = get_action(&argv, arglist2); //verify the arg for -net or -host
267   if (!*argv) help_exit("setroute");
268 
269   memset(&rt, 0, sizeof(struct rtentry));
270   targetip = *argv++;
271 
272   netmask = strchr(targetip, '/');
273   if (netmask) {
274     *netmask++ = 0;
275     //used to verify the netmask and route conflict.
276     (((struct sockaddr_in *)&rt.rt_genmask)->sin_addr.s_addr)
277       = htonl((1<<(32-atolx_range(netmask, 0, 32)))-1);
278     rt.rt_genmask.sa_family = AF_INET;
279     netmask = 0;
280   } else netmask = "default";
281 
282   is_net_or_host = get_hostname(targetip, (void *)&rt.rt_dst);
283 
284   if (arg2_action) is_net_or_host = arg2_action & 1;
285   rt.rt_flags = ((is_net_or_host) ? RTF_UP : (RTF_UP | RTF_HOST));
286 
287   get_next_params(argv, &rt, (char **)&netmask);
288   verify_netmask(&rt, (char *)netmask);
289 
290   if ((action == 1) && (rt.rt_flags & RTF_HOST))
291     (((struct sockaddr_in *)&((rt).rt_genmask))->sin_addr.s_addr) = INVALID_ADDR;
292 
293   sokfd = xsocket(AF_INET, SOCK_DGRAM, 0);
294   if (action == 1) xioctl(sokfd, SIOCADDRT, &rt);
295   else xioctl(sokfd, SIOCDELRT, &rt);
296   xclose(sokfd);
297 }
298 
299 /*
300  * get prefix len (if any) and remove the prefix from target ip.
301  * if no prefix then set default prefix len.
302  */
is_prefix_inet6(char ** tip,struct in6_rtmsg * rt)303 static void is_prefix_inet6(char **tip, struct in6_rtmsg *rt)
304 {
305   unsigned long plen;
306   char *prefix = strchr(*tip, '/');
307 
308   if (prefix) {
309     *prefix = '\0';
310     plen = atolx_range(prefix + 1, 0, 128); //DEFAULT_PREFIXLEN);
311   } else plen = DEFAULT_PREFIXLEN;
312 
313   rt->rtmsg_flags = (plen == DEFAULT_PREFIXLEN) ? (RTF_UP | RTF_HOST) : RTF_UP;
314   rt->rtmsg_dst_len = plen;
315 }
316 
317 /*
318  * used to get the params like: metric, gw, dev and their values.
319  * additionally set the flag values for mod and dyn.
320  */
get_next_params_inet6(char ** argv,struct sockaddr_in6 * sock_in6,struct in6_rtmsg * rt,char ** dev_name)321 static void get_next_params_inet6(char **argv, struct sockaddr_in6 *sock_in6, struct in6_rtmsg *rt, char **dev_name)
322 {
323   for (;*argv;argv++) {
324     if (!strcmp(*argv, "mod")) rt->rtmsg_flags |= RTF_MODIFIED;
325     else if (!strcmp(*argv, "dyn")) rt->rtmsg_flags |= RTF_DYNAMIC;
326     else {
327       if (!argv[1]) help_exit(0);
328 
329       if (!strcmp(*argv, "metric"))
330         rt->rtmsg_metric = atolx_range(argv[1], 0, ULONG_MAX);
331       else if (!strcmp(*argv, "gw")) {
332         //route packets via a gateway.
333         if (!(rt->rtmsg_flags & RTF_GATEWAY)) {
334           if (!get_addrinfo(argv[1], (struct sockaddr_in6 *) &sock_in6)) {
335             memcpy(&rt->rtmsg_gateway, sock_in6->sin6_addr.s6_addr, sizeof(struct in6_addr));
336             rt->rtmsg_flags |= RTF_GATEWAY;
337           } else perror_exit("resolving '%s'", argv[1]);
338         } else help_exit(0);
339       } else if (!strcmp(*argv, "dev")) {
340         if (!*dev_name) *dev_name = argv[1];
341       } else help_exit(0);
342       argv++;
343     }
344   }
345 }
346 
347 // add/del a route.
setroute_inet6(char ** argv)348 static void setroute_inet6(char **argv)
349 {
350   struct sockaddr_in6 sock_in6;
351   struct in6_rtmsg rt;
352   char *targetip, *dev_name = 0;
353   int sockfd, action = get_action(&argv, arglist1);
354 
355   if (!action || !*argv) help_exit(0);
356   memset(&sock_in6, 0, sizeof(struct sockaddr_in6));
357   memset(&rt, 0, sizeof(struct in6_rtmsg));
358   targetip = *argv++;
359   if (!*argv) help_exit(0);
360 
361   if (!strcmp(targetip, "default")) {
362     rt.rtmsg_flags = RTF_UP;
363     rt.rtmsg_dst_len = 0;
364   } else {
365     is_prefix_inet6((char **)&targetip, &rt);
366     if (get_addrinfo(targetip, (struct sockaddr_in6 *) &sock_in6))
367       perror_exit("resolving '%s'", targetip);
368   }
369   rt.rtmsg_metric = 1; //default metric.
370   memcpy(&rt.rtmsg_dst, sock_in6.sin6_addr.s6_addr, sizeof(struct in6_addr));
371   get_next_params_inet6(argv, &sock_in6, &rt, (char **)&dev_name);
372 
373   sockfd = xsocket(AF_INET6, SOCK_DGRAM, 0);
374   if (dev_name) {
375     char ifre_buf[sizeof(struct ifreq)] = {0,};
376     struct ifreq *ifre = (struct ifreq*)ifre_buf;
377     xstrncpy(ifre->ifr_name, dev_name, IFNAMSIZ);
378     xioctl(sockfd, SIOGIFINDEX, ifre);
379     rt.rtmsg_ifindex = ifre->ifr_ifindex;
380   }
381   if (action == 1) xioctl(sockfd, SIOCADDRT, &rt);
382   else xioctl(sockfd, SIOCDELRT, &rt);
383   xclose(sockfd);
384 }
385 
386 /*
387  * format the dest and src address in ipv6 format.
388  * e.g. 2002:6b6d:26c8:d:ea03:9aff:fe65:9d62
389  */
ipv6_addr_formating(char * ptr,char * addr)390 static void ipv6_addr_formating(char *ptr, char *addr)
391 {
392   int i = 0;
393   while (i <= IPV6_ADDR_LEN) {
394     if (!*ptr) {
395       if (i == IPV6_ADDR_LEN) {
396         addr[IPV6_ADDR_LEN - 1] = 0; //NULL terminating the ':' separated address.
397         break;
398       }
399       error_exit("IPv6 ip format error");
400     }
401     addr[i++] = *ptr++;
402     if (!((i+1) % 5)) addr[i++] = ':'; //put ':' after 4th bit
403   }
404 }
405 
display_routes6(void)406 static void display_routes6(void)
407 {
408   char iface[16] = {0,}, ipv6_dest_addr[41];
409   char ipv6_src_addr[41], flag_val[10], buf2[INET6_ADDRSTRLEN];
410   int prefixlen, metric, use, refcount, flag, items = 0;
411   unsigned char buf[sizeof(struct in6_addr)];
412 
413   FILE *fp = xfopen("/proc/net/ipv6_route", "r");
414 
415   xprintf("Kernel IPv6 routing table\n"
416       "%-43s%-40s Flags Metric Ref    Use Iface\n", "Destination", "Next Hop");
417 
418   while ((items = fscanf(fp, "%32s%x%*s%*x%32s%x%x%x%x%15s\n", ipv6_dest_addr+8,
419           &prefixlen, ipv6_src_addr+8, &metric, &use, &refcount, &flag,
420           iface)) == 8)
421   {
422     if (!(flag & RTF_UP)) continue; //skip down interfaces.
423 
424     //ipv6_dest_addr+8: as the values are filled from the 8th location of the array.
425     ipv6_addr_formating(ipv6_dest_addr+8, ipv6_dest_addr);
426     ipv6_addr_formating(ipv6_src_addr+8, ipv6_src_addr);
427 
428     get_flag_value(flag_val, flag);
429     if (inet_pton(AF_INET6, ipv6_dest_addr, buf) <= 0) perror_exit("inet");
430     if (inet_ntop(AF_INET6, buf, buf2, INET6_ADDRSTRLEN))
431       sprintf(toybuf, "%s/%d", buf2, prefixlen);
432 
433     if (inet_pton(AF_INET6, ipv6_src_addr, buf) <= 0) perror_exit("inet");
434     if (inet_ntop(AF_INET6, buf, buf2, INET6_ADDRSTRLEN))
435       xprintf("%-43s %-39s %-5s %-6d %-4d %5d %-8s\n",
436           toybuf, buf2, flag_val, metric, refcount, use, iface);
437   }
438   if ((items > 0) && feof(fp)) perror_exit("fscanf");
439 
440   fclose(fp);
441 }
442 
route_main(void)443 void route_main(void)
444 {
445   if (!TT.family) TT.family = "inet";
446   if (!*toys.optargs) {
447     if (!strcmp(TT.family, "inet")) display_routes();
448     else if (!strcmp(TT.family, "inet6")) display_routes6();
449     else help_exit(0);
450   } else {
451     if (!strcmp(TT.family, "inet6")) setroute_inet6(toys.optargs);
452     else setroute(toys.optargs);
453   }
454 }
455