github.com/ratrocket/u-root@v0.0.0-20180201221235-1cf9f48ee2cf/cmds/ip/ip.go (about)

     1  // Copyright 2012-2017 the u-root Authors. All rights reserved
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"flag"
     9  	"fmt"
    10  	"io"
    11  	"io/ioutil"
    12  	l "log"
    13  	"math"
    14  	"os"
    15  	"strings"
    16  
    17  	"github.com/vishvananda/netlink"
    18  )
    19  
    20  // you will notice that I suck at parsers. That said, here is the method to my madness.
    21  // The language of ip is not super consistent and has lots of convenience shortcuts.
    22  // The BNF it shows you doesn't show them.
    23  // The inputs is just the set of args.
    24  // It's very short.
    25  // Each token is just a string and we need not produce terminals with them -- they can
    26  // just be the terminals and we can switch on them.
    27  // The cursor is always our current token pointer. We do a dumb recursive descent parser
    28  // and accumulate information into a global set of variables. At any point we can see into the
    29  // whole set of args and see where we are. We can indicate at each point what we're expecting so
    30  // that in usage() or recover() we can tell the user exactly what we wanted, unlike IP,
    31  // which just barfs a whole (incorrect) BNF at you when you do anything wrong.
    32  // To handle errors in too few arguments, we just do a recover block. That lets us blindly
    33  // reference the arg[] array without having to check the length everywhere.
    34  
    35  // Note the plethora of globals. The reason is simple: we parse one command, do it, and quit.
    36  // It doesn't make sense to write this otherwise.
    37  var (
    38  	// Cursor is out next token pointer.
    39  	// The language of this command doesn't require much more.
    40  	cursor    int
    41  	arg       []string
    42  	whatIWant []string
    43  	log       = l.New(os.Stdout, "ip: ", 0)
    44  
    45  	addrScopes = map[netlink.Scope]string{
    46  		netlink.SCOPE_UNIVERSE: "global",
    47  		netlink.SCOPE_HOST:     "host",
    48  		netlink.SCOPE_SITE:     "site",
    49  		netlink.SCOPE_LINK:     "link",
    50  		netlink.SCOPE_NOWHERE:  "nowhere",
    51  	}
    52  )
    53  
    54  // the pattern:
    55  // at each level parse off arg[0]. If it matches, continue. If it does not, all error with how far you got, what arg you saw,
    56  // and why it did not work out.
    57  
    58  func usage() {
    59  	log.Fatalf("This was fine: '%v', and this was left, '%v', and this was not understood, '%v'; only options are '%v'",
    60  		arg[0:cursor], arg[cursor:], arg[cursor], whatIWant)
    61  }
    62  
    63  func one(cmd string, cmds []string) string {
    64  	var x, n int
    65  	for i, v := range cmds {
    66  		if strings.HasPrefix(v, cmd) {
    67  			n++
    68  			x = i
    69  		}
    70  	}
    71  	if n == 1 {
    72  		return cmds[x]
    73  	}
    74  	return ""
    75  }
    76  
    77  // in the ip command, turns out 'dev' is a noise word.
    78  // The BNF is not right there either.
    79  // Always make it optional.
    80  func dev() netlink.Link {
    81  	cursor++
    82  	whatIWant = []string{"dev", "device name"}
    83  	if arg[cursor] == "dev" {
    84  		cursor++
    85  	}
    86  	whatIWant = []string{"device name"}
    87  	iface, err := netlink.LinkByName(arg[cursor])
    88  	if err != nil {
    89  		usage()
    90  	}
    91  	return iface
    92  }
    93  
    94  func showLinks(w io.Writer, withAddresses bool) {
    95  	ifaces, err := netlink.LinkList()
    96  	if err != nil {
    97  		log.Fatalf("Can't enumerate interfaces? %v", err)
    98  	}
    99  
   100  	for _, v := range ifaces {
   101  		l := v.Attrs()
   102  
   103  		fmt.Fprintf(w, "%d: %s: <%s> mtu %d state %s\n", l.Index, l.Name,
   104  			strings.Replace(strings.ToUpper(fmt.Sprintf("%s", l.Flags)), "|", ",", -1),
   105  			l.MTU, strings.ToUpper(l.OperState.String()))
   106  
   107  		fmt.Fprintf(w, "    link/%s %s\n", l.EncapType, l.HardwareAddr)
   108  
   109  		if withAddresses {
   110  			showLinkAddresses(w, v)
   111  		}
   112  	}
   113  }
   114  
   115  func showLinkAddresses(w io.Writer, link netlink.Link) {
   116  	addrs, err := netlink.AddrList(link, netlink.FAMILY_ALL)
   117  	if err != nil {
   118  		log.Printf("Can't enumerate addresses")
   119  	}
   120  
   121  	for _, addr := range addrs {
   122  
   123  		var inet string
   124  		switch len(addr.IPNet.IP) {
   125  		case 4:
   126  			inet = "inet"
   127  		case 16:
   128  			inet = "inet6"
   129  		default:
   130  			log.Fatalf("Can't figure out IP protocol version")
   131  		}
   132  
   133  		fmt.Fprintf(w, "    %s %s", inet, addr.Peer)
   134  		if addr.Broadcast != nil {
   135  			fmt.Fprintf(w, " brd %s", addr.Broadcast)
   136  		}
   137  		fmt.Fprintf(w, " scope %s %s\n", addrScopes[netlink.Scope(addr.Scope)], addr.Label)
   138  
   139  		var validLft, preferredLft string
   140  		// TODO: fix vishnavanda/netlink. *Lft should be uint32, not int.
   141  		if uint32(addr.PreferedLft) == math.MaxUint32 {
   142  			preferredLft = "forever"
   143  		} else {
   144  			preferredLft = fmt.Sprintf("%dsec", addr.PreferedLft)
   145  		}
   146  		if uint32(addr.ValidLft) == math.MaxUint32 {
   147  			validLft = "forever"
   148  		} else {
   149  			validLft = fmt.Sprintf("%dsec", addr.ValidLft)
   150  		}
   151  		fmt.Fprintf(w, "       valid_lft %s preferred_lft %s\n", validLft, preferredLft)
   152  	}
   153  }
   154  
   155  func addrip() {
   156  	var err error
   157  	var addr *netlink.Addr
   158  	if len(arg) == 1 {
   159  		showLinks(os.Stdout, true)
   160  		return
   161  	}
   162  	cursor++
   163  	whatIWant = []string{"add", "del"}
   164  	cmd := arg[cursor]
   165  
   166  	c := one(cmd, whatIWant)
   167  	switch c {
   168  	case "add", "del":
   169  		cursor++
   170  		whatIWant = []string{"CIDR format address"}
   171  		addr, err = netlink.ParseAddr(arg[cursor])
   172  		if err != nil {
   173  			usage()
   174  		}
   175  	default:
   176  		usage()
   177  	}
   178  	iface := dev()
   179  	switch c {
   180  	case "add":
   181  		if err := netlink.AddrAdd(iface, addr); err != nil {
   182  			log.Fatalf("Adding %v to %v failed: %v", arg[1], arg[2], err)
   183  		}
   184  	case "del":
   185  		if err := netlink.AddrDel(iface, addr); err != nil {
   186  			log.Fatalf("Deleting %v from %v failed: %v", arg[1], arg[2], err)
   187  		}
   188  	default:
   189  		log.Fatalf("devip: arg[0] changed: can't happen")
   190  	}
   191  	return
   192  
   193  }
   194  
   195  func linkshow() {
   196  	cursor++
   197  	whatIWant = []string{"<nothing>", "<device name>"}
   198  	if len(arg[cursor:]) == 0 {
   199  		showLinks(os.Stdout, false)
   200  	}
   201  }
   202  
   203  func linkset() {
   204  	iface := dev()
   205  	cursor++
   206  	whatIWant = []string{"up", "down"}
   207  	switch one(arg[cursor], whatIWant) {
   208  	case "up":
   209  		if err := netlink.LinkSetUp(iface); err != nil {
   210  			log.Fatalf("%v can't make it up: %v", iface, err)
   211  		}
   212  	case "down":
   213  		if err := netlink.LinkSetDown(iface); err != nil {
   214  			log.Fatalf("%v can't make it down: %v", iface, err)
   215  		}
   216  	default:
   217  		usage()
   218  	}
   219  }
   220  
   221  func link() {
   222  	if len(arg) == 1 {
   223  		linkshow()
   224  		return
   225  	}
   226  
   227  	cursor++
   228  	whatIWant = []string{"show", "set"}
   229  	cmd := arg[cursor]
   230  
   231  	switch one(cmd, whatIWant) {
   232  	case "show":
   233  		linkshow()
   234  	case "set":
   235  		linkset()
   236  	default:
   237  		usage()
   238  	}
   239  	return
   240  }
   241  
   242  func routeshow() {
   243  	if b, err := ioutil.ReadFile("/proc/net/route"); err == nil {
   244  		log.Printf("%s", string(b))
   245  	} else {
   246  		log.Fatalf("Route show failed: %v", err)
   247  	}
   248  }
   249  
   250  func nodespec() string {
   251  	cursor++
   252  	whatIWant = []string{"default", "CIDR"}
   253  	return arg[cursor]
   254  }
   255  
   256  func nexthop() (string, *netlink.Addr) {
   257  	cursor++
   258  	whatIWant = []string{"via"}
   259  	if arg[cursor] != "via" {
   260  		usage()
   261  	}
   262  	nh := arg[cursor]
   263  	cursor++
   264  	whatIWant = []string{"Gateway CIDR"}
   265  	addr, err := netlink.ParseAddr(arg[cursor])
   266  	if err != nil {
   267  		log.Fatalf("Gateway CIDR: %v", err)
   268  	}
   269  	return nh, addr
   270  }
   271  
   272  func routeadddefault() {
   273  	nh, nhval := nexthop()
   274  	// TODO: NHFLAGS.
   275  	l := dev()
   276  	switch nh {
   277  	case "via":
   278  		log.Printf("Add default route %v via %v", nhval, l)
   279  		r := &netlink.Route{LinkIndex: l.Attrs().Index, Gw: nhval.IPNet.IP}
   280  		if err := netlink.RouteAdd(r); err != nil {
   281  			log.Fatalf("Add default route: %v", err)
   282  		}
   283  
   284  	default:
   285  		usage()
   286  	}
   287  }
   288  
   289  func routeadd() {
   290  	ns := nodespec()
   291  	switch ns {
   292  	case "default":
   293  		routeadddefault()
   294  	default:
   295  		usage()
   296  	}
   297  }
   298  
   299  func route() {
   300  	cursor++
   301  	if len(arg[cursor:]) == 0 {
   302  		routeshow()
   303  		return
   304  	}
   305  
   306  	whatIWant = []string{"show", "add"}
   307  	switch one(arg[cursor], whatIWant) {
   308  	case "show":
   309  		routeshow()
   310  	case "add":
   311  		routeadd()
   312  	default:
   313  		usage()
   314  	}
   315  
   316  }
   317  
   318  func main() {
   319  	// When this is embedded in busybox we need to reinit some things.
   320  	whatIWant = []string{"addr", "route", "link"}
   321  	cursor = 0
   322  	flag.Parse()
   323  	arg = flag.Args()
   324  
   325  	defer func() {
   326  		switch err := recover().(type) {
   327  		case nil:
   328  		case error:
   329  			if strings.Contains(err.Error(), "index out of range") {
   330  				log.Fatalf("Args: %v, I got to arg %v, I wanted %v after that", arg, cursor, whatIWant)
   331  			} else if strings.Contains(err.Error(), "slice bounds out of range") {
   332  				log.Fatalf("Args: %v, I got to arg %v, I wanted %v after that", arg, cursor, whatIWant)
   333  			}
   334  			log.Fatalf("Bummer: %v", err)
   335  		default:
   336  			log.Fatalf("unexpected panic value: %T(%v)", err, err)
   337  		}
   338  	}()
   339  
   340  	// The ip command doesn't actually follow the BNF it prints on error.
   341  	// There are lots of handy shortcuts that people will expect.
   342  	switch one(arg[cursor], whatIWant) {
   343  	case "addr":
   344  		addrip()
   345  	case "link":
   346  		link()
   347  	case "route":
   348  		route()
   349  	default:
   350  		usage()
   351  	}
   352  }