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