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 }