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