github.com/shaardie/u-root@v4.0.1-0.20190127173353-f24a1c26aa2e+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 default: 257 addr, err := netlink.ParseAddr(arg[cursor]) 258 if err != nil { 259 return usage() 260 } 261 d, err := dev() 262 if err != nil { 263 return usage() 264 } 265 r := &netlink.Route{LinkIndex: d.Attrs().Index, Dst: addr.IPNet} 266 if err := netlink.RouteAdd(r); err != nil { 267 return fmt.Errorf("error adding route %s -> %s: %v", addr, d, err) 268 } 269 return nil 270 } 271 } 272 273 func route() error { 274 cursor++ 275 if len(arg[cursor:]) == 0 { 276 return routeshow() 277 } 278 279 whatIWant = []string{"show", "add"} 280 switch one(arg[cursor], whatIWant) { 281 case "show": 282 return routeshow() 283 case "add": 284 return routeadd() 285 } 286 return usage() 287 } 288 289 func main() { 290 // When this is embedded in busybox we need to reinit some things. 291 whatIWant = []string{"addr", "route", "link"} 292 cursor = 0 293 flag.Parse() 294 arg = flag.Args() 295 296 defer func() { 297 switch err := recover().(type) { 298 case nil: 299 case error: 300 if strings.Contains(err.Error(), "index out of range") { 301 log.Fatalf("Args: %v, I got to arg %v, I wanted %v after that", arg, cursor, whatIWant) 302 } else if strings.Contains(err.Error(), "slice bounds out of range") { 303 log.Fatalf("Args: %v, I got to arg %v, I wanted %v after that", arg, cursor, whatIWant) 304 } 305 log.Fatalf("Bummer: %v", err) 306 default: 307 log.Fatalf("unexpected panic value: %T(%v)", err, err) 308 } 309 }() 310 311 // The ip command doesn't actually follow the BNF it prints on error. 312 // There are lots of handy shortcuts that people will expect. 313 var err error 314 switch one(arg[cursor], whatIWant) { 315 case "addr": 316 err = addrip() 317 case "link": 318 err = link() 319 case "route": 320 err = route() 321 default: 322 usage() 323 } 324 if err != nil { 325 log.Fatal(err) 326 } 327 328 }