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