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