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 }