github.com/mem/u-root@v2.0.1-0.20181004165302-9b18b4636a33+incompatible/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  	"fmt"
     9  	flag "github.com/spf13/pflag"
    10  	"io/ioutil"
    11  	l "log"
    12  	"net"
    13  	"os"
    14  	"strings"
    15  
    16  	"github.com/vishvananda/netlink"
    17  )
    18  
    19  // The language implemented by the standard 'ip' is not super consistent
    20  // and has lots of convenience shortcuts.
    21  // The BNF the standard ip  shows you doesn't show many of these short cuts, and
    22  // it is wrong in other ways.
    23  // For this ip command:.
    24  // The inputs is just the set of args.
    25  // The input is very short -- it's not a program!
    26  // Each token is just a string and we need not produce terminals with them -- they can
    27  // just be the terminals and we can switch on them.
    28  // The cursor is always our current token pointer. We do a simple recursive descent parser
    29  // and accumulate information into a global set of variables. At any point we can see into the
    30  // whole set of args and see where we are. We can indicate at each point what we're expecting so
    31  // that in usage() or recover() we can tell the user exactly what we wanted, unlike the standard ip,
    32  // which just dumps a whole (incorrect) BNF at you when you do anything wrong.
    33  // To handle errors in too few arguments, we just do a recover block. That lets us blindly
    34  // reference the arg[] array without having to check the length everywhere.
    35  
    36  // RE: the use of globals. The reason is simple: we parse one command, do it, and quit.
    37  // It doesn't make sense to write this otherwise.
    38  var (
    39  	// Cursor is out next token pointer.
    40  	// The language of this command doesn't require much more.
    41  	cursor    int
    42  	arg       []string
    43  	whatIWant []string
    44  	log       = l.New(os.Stdout, "ip: ", 0)
    45  
    46  	addrScopes = map[netlink.Scope]string{
    47  		netlink.SCOPE_UNIVERSE: "global",
    48  		netlink.SCOPE_HOST:     "host",
    49  		netlink.SCOPE_SITE:     "site",
    50  		netlink.SCOPE_LINK:     "link",
    51  		netlink.SCOPE_NOWHERE:  "nowhere",
    52  	}
    53  )
    54  
    55  // the pattern:
    56  // 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,
    57  // and why it did not work out.
    58  
    59  func usage() error {
    60  	return fmt.Errorf("This was fine: '%v', and this was left, '%v', and this was not understood, '%v'; only options are '%v'",
    61  		arg[0:cursor], arg[cursor:], arg[cursor], whatIWant)
    62  }
    63  
    64  func one(cmd string, cmds []string) string {
    65  	var x, n int
    66  	for i, v := range cmds {
    67  		if strings.HasPrefix(v, cmd) {
    68  			n++
    69  			x = i
    70  		}
    71  	}
    72  	if n == 1 {
    73  		return cmds[x]
    74  	}
    75  	return ""
    76  }
    77  
    78  // in the ip command, turns out 'dev' is a noise word.
    79  // The BNF it shows is not right in that case.
    80  // Always make 'dev' optional.
    81  func dev() (netlink.Link, error) {
    82  	cursor++
    83  	whatIWant = []string{"dev", "device name"}
    84  	if arg[cursor] == "dev" {
    85  		cursor++
    86  	}
    87  	whatIWant = []string{"device name"}
    88  	return netlink.LinkByName(arg[cursor])
    89  }
    90  
    91  func addrip() error {
    92  	var err error
    93  	var addr *netlink.Addr
    94  	if len(arg) == 1 {
    95  		return showLinks(os.Stdout, true)
    96  	}
    97  	cursor++
    98  	whatIWant = []string{"add", "del"}
    99  	cmd := arg[cursor]
   100  
   101  	c := one(cmd, whatIWant)
   102  	switch c {
   103  	case "add", "del":
   104  		cursor++
   105  		whatIWant = []string{"CIDR format address"}
   106  		addr, err = netlink.ParseAddr(arg[cursor])
   107  		if err != nil {
   108  			return err
   109  		}
   110  	default:
   111  		return usage()
   112  	}
   113  	iface, err := dev()
   114  	if err != nil {
   115  		return err
   116  	}
   117  	switch c {
   118  	case "add":
   119  		if err := netlink.AddrAdd(iface, addr); err != nil {
   120  			return fmt.Errorf("Adding %v to %v failed: %v", arg[1], arg[2], err)
   121  		}
   122  	case "del":
   123  		if err := netlink.AddrDel(iface, addr); err != nil {
   124  			return fmt.Errorf("Deleting %v from %v failed: %v", arg[1], arg[2], err)
   125  		}
   126  	default:
   127  		return fmt.Errorf("devip: arg[0] changed: can't happen")
   128  	}
   129  	return nil
   130  }
   131  
   132  func linkshow() error {
   133  	cursor++
   134  	whatIWant = []string{"<nothing>", "<device name>"}
   135  	if len(arg[cursor:]) == 0 {
   136  		return showLinks(os.Stdout, false)
   137  	}
   138  	return nil
   139  }
   140  
   141  func setHardwareAddress(iface netlink.Link) error {
   142  	cursor++
   143  	hwAddr, err := net.ParseMAC(arg[cursor])
   144  	if err != nil {
   145  
   146  		return fmt.Errorf("%v cant parse mac addr %v: %v", iface, hwAddr, err)
   147  	}
   148  	err = netlink.LinkSetHardwareAddr(iface, hwAddr)
   149  	if err != nil {
   150  		return fmt.Errorf("%v cant set mac addr %v: %v", iface, hwAddr, err)
   151  	}
   152  	return nil
   153  }
   154  
   155  func linkset() error {
   156  	iface, err := dev()
   157  	if err != nil {
   158  		return err
   159  	}
   160  
   161  	cursor++
   162  	whatIWant = []string{"address", "up", "down"}
   163  	switch one(arg[cursor], whatIWant) {
   164  	case "address":
   165  		return setHardwareAddress(iface)
   166  	case "up":
   167  		if err := netlink.LinkSetUp(iface); err != nil {
   168  			return fmt.Errorf("%v can't make it up: %v", iface, err)
   169  		}
   170  	case "down":
   171  		if err := netlink.LinkSetDown(iface); err != nil {
   172  			return fmt.Errorf("%v can't make it down: %v", iface, err)
   173  		}
   174  	default:
   175  		return usage()
   176  	}
   177  	return nil
   178  }
   179  
   180  func link() error {
   181  	if len(arg) == 1 {
   182  		return linkshow()
   183  	}
   184  
   185  	cursor++
   186  	whatIWant = []string{"show", "set"}
   187  	cmd := arg[cursor]
   188  
   189  	switch one(cmd, whatIWant) {
   190  	case "show":
   191  		return linkshow()
   192  	case "set":
   193  		return linkset()
   194  	}
   195  	return usage()
   196  }
   197  
   198  func routeshow() error {
   199  	b, err := ioutil.ReadFile("/proc/net/route")
   200  	if err != nil {
   201  		return fmt.Errorf("Route show failed: %v", err)
   202  	}
   203  	log.Printf("%s", string(b))
   204  	return nil
   205  }
   206  
   207  func nodespec() string {
   208  	cursor++
   209  	whatIWant = []string{"default", "CIDR"}
   210  	return arg[cursor]
   211  }
   212  
   213  func nexthop() (string, *netlink.Addr, error) {
   214  	cursor++
   215  	whatIWant = []string{"via"}
   216  	if arg[cursor] != "via" {
   217  		return "", nil, usage()
   218  	}
   219  	nh := arg[cursor]
   220  	cursor++
   221  	whatIWant = []string{"Gateway CIDR"}
   222  	addr, err := netlink.ParseAddr(arg[cursor])
   223  	if err != nil {
   224  		return "", nil, fmt.Errorf("Gateway CIDR: %v", err)
   225  	}
   226  	return nh, addr, nil
   227  }
   228  
   229  func routeadddefault() error {
   230  	nh, nhval, err := nexthop()
   231  	if err != nil {
   232  		return err
   233  	}
   234  	// TODO: NHFLAGS.
   235  	l, err := dev()
   236  	if err != nil {
   237  		return err
   238  	}
   239  	switch nh {
   240  	case "via":
   241  		log.Printf("Add default route %v via %v", nhval, l)
   242  		r := &netlink.Route{LinkIndex: l.Attrs().Index, Gw: nhval.IPNet.IP}
   243  		if err := netlink.RouteAdd(r); err != nil {
   244  			return fmt.Errorf("Add default route: %v", err)
   245  		}
   246  		return nil
   247  	}
   248  	return usage()
   249  }
   250  
   251  func routeadd() error {
   252  	ns := nodespec()
   253  	switch ns {
   254  	case "default":
   255  		return routeadddefault()
   256  	}
   257  	return usage()
   258  }
   259  
   260  func route() error {
   261  	cursor++
   262  	if len(arg[cursor:]) == 0 {
   263  		return routeshow()
   264  	}
   265  
   266  	whatIWant = []string{"show", "add"}
   267  	switch one(arg[cursor], whatIWant) {
   268  	case "show":
   269  		return routeshow()
   270  	case "add":
   271  		return routeadd()
   272  	}
   273  	return usage()
   274  }
   275  
   276  func main() {
   277  	// When this is embedded in busybox we need to reinit some things.
   278  	whatIWant = []string{"addr", "route", "link"}
   279  	cursor = 0
   280  	flag.Parse()
   281  	arg = flag.Args()
   282  
   283  	defer func() {
   284  		switch err := recover().(type) {
   285  		case nil:
   286  		case error:
   287  			if strings.Contains(err.Error(), "index out of range") {
   288  				log.Fatalf("Args: %v, I got to arg %v, I wanted %v after that", arg, cursor, whatIWant)
   289  			} else if strings.Contains(err.Error(), "slice bounds out of range") {
   290  				log.Fatalf("Args: %v, I got to arg %v, I wanted %v after that", arg, cursor, whatIWant)
   291  			}
   292  			log.Fatalf("Bummer: %v", err)
   293  		default:
   294  			log.Fatalf("unexpected panic value: %T(%v)", err, err)
   295  		}
   296  	}()
   297  
   298  	// The ip command doesn't actually follow the BNF it prints on error.
   299  	// There are lots of handy shortcuts that people will expect.
   300  	var err error
   301  	switch one(arg[cursor], whatIWant) {
   302  	case "addr":
   303  		err = addrip()
   304  	case "link":
   305  		err = link()
   306  	case "route":
   307  		err = route()
   308  	default:
   309  		usage()
   310  	}
   311  	if err != nil {
   312  		log.Fatal(err)
   313  	}
   314  
   315  }