xref: /third_party/toybox/toys/pending/route.c (revision 0f66f451)
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
16USE_ROUTE(NEWTOY(route, "?neA:", TOYFLAG_BIN))
17config 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
53GLOBALS(
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
61struct _arglist {
62  char *arg;
63  int action;
64};
65
66static struct _arglist arglist1[] = {
67  { "add", 1 }, { "del", 2 },
68  { "delete", 2 }, { NULL, 0 }
69};
70
71static struct _arglist arglist2[] = {
72  { "-net", 1 }, { "-host", 2 },
73  { NULL, 0 }
74};
75
76// to get the host name from the given ip.
77static 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.
97static 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
115static 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.
129static 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 */
173static 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 */
191static 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.
241static 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.
257static 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 */
303static 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 */
321static 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.
348static 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 */
390static 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
406static 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
443void 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