1
1
package ip
2
2
3
3
import (
4
- "strings"
4
+ "fmt"
5
+ "net"
6
+ "strconv"
5
7
6
- "github.com/lxc/incus/v6/shared/subprocess"
8
+ "github.com/vishvananda/netlink"
9
+ "golang.org/x/sys/unix"
7
10
)
8
11
9
12
// Route represents arguments for route manipulation.
@@ -18,129 +21,290 @@ type Route struct {
18
21
VRF string
19
22
}
20
23
21
- // Add adds new route.
22
- func (r * Route ) Add () error {
23
- cmd := []string {r .Family , "route" , "add" }
24
+ func (r * Route ) netlinkRoute () (* netlink.Route , error ) {
25
+ link , err := netlink .LinkByName (r .DevName )
26
+ if err != nil {
27
+ return nil , err
28
+ }
29
+
30
+ family , err := r .netlinkFamily ()
31
+ if err != nil {
32
+ return nil , err
33
+ }
34
+
35
+ route := & netlink.Route {
36
+ LinkIndex : link .Attrs ().Index ,
37
+ Family : family ,
38
+ }
39
+
40
+ if r .Route != "" {
41
+ _ , dst , err := net .ParseCIDR (r .Route )
42
+ if err != nil {
43
+ return nil , err
44
+ }
45
+
46
+ route .Dst = dst
47
+ }
48
+
24
49
if r .Table != "" {
25
- cmd = append (cmd , "table" , r .Table )
50
+ tableID , err := r .tableID ()
51
+ if err != nil {
52
+ return nil , err
53
+ }
54
+
55
+ route .Table = tableID
56
+ } else if r .VRF != "" {
57
+ vrfDev , err := netlink .LinkByName (r .VRF )
58
+ if err != nil {
59
+ return nil , err
60
+ }
61
+
62
+ vrf , ok := vrfDev .(* netlink.Vrf )
63
+ if ! ok {
64
+ return nil , fmt .Errorf ("%q is not a vrf" , r .VRF )
65
+ }
66
+
67
+ route .Table = int (vrf .Table )
26
68
}
27
69
28
70
if r .Via != "" {
29
- cmd = append (cmd , "via" , r .Via )
71
+ via := net .ParseIP (r .Via )
72
+ if via == nil {
73
+ return nil , fmt .Errorf ("invalid via address %q" , r .Via )
74
+ }
75
+
76
+ route .Gw = via
30
77
}
31
78
32
- cmd = append (cmd , r .Route , "dev" , r .DevName )
33
79
if r .Src != "" {
34
- cmd = append (cmd , "src" , r .Src )
80
+ src := net .ParseIP (r .Src )
81
+ if src == nil {
82
+ return nil , fmt .Errorf ("invalid src address %q" , r .Src )
83
+ }
84
+
85
+ route .Src = src
35
86
}
36
87
37
88
if r .Proto != "" {
38
- cmd = append (cmd , "proto" , r .Proto )
89
+ proto , err := r .netlinkProto ()
90
+ if err != nil {
91
+ return nil , err
92
+ }
93
+
94
+ route .Protocol = proto
39
95
}
40
96
41
- if r .VRF != "" {
42
- cmd = append (cmd , "vrf" , r .VRF )
97
+ return route , nil
98
+ }
99
+
100
+ func (r * Route ) tableID () (int , error ) {
101
+ switch r .Table {
102
+ case "default" :
103
+ return unix .RT_TABLE_DEFAULT , nil
104
+ case "main" :
105
+ return unix .RT_TABLE_MAIN , nil
106
+ case "local" :
107
+ return unix .RT_TABLE_LOCAL , nil
108
+ default :
109
+ return strconv .Atoi (r .Table )
43
110
}
111
+ }
112
+
113
+ func (r * Route ) netlinkFamily () (int , error ) {
114
+ switch r .Family {
115
+ case FamilyV4 :
116
+ return netlink .FAMILY_V4 , nil
117
+ case FamilyV6 :
118
+ return netlink .FAMILY_V6 , nil
119
+ default :
120
+ return 0 , fmt .Errorf ("invalid family %q" , r .Family )
121
+ }
122
+ }
123
+
124
+ func (r * Route ) netlinkProto () (netlink.RouteProtocol , error ) {
125
+ switch r .Proto {
126
+ case "babel" :
127
+ return unix .RTPROT_BABEL , nil
128
+ case "bgp" :
129
+ return unix .RTPROT_BGP , nil
130
+ case "bird" :
131
+ return unix .RTPROT_BIRD , nil
132
+ case "boot" :
133
+ return unix .RTPROT_BOOT , nil
134
+ case "dhcp" :
135
+ return unix .RTPROT_DHCP , nil
136
+ case "dnrouted" :
137
+ return unix .RTPROT_DNROUTED , nil
138
+ case "eigrp" :
139
+ return unix .RTPROT_EIGRP , nil
140
+ case "gated" :
141
+ return unix .RTPROT_GATED , nil
142
+ case "isis" :
143
+ return unix .RTPROT_ISIS , nil
144
+ case "keepalived" :
145
+ return unix .RTPROT_KEEPALIVED , nil
146
+ case "kernel" :
147
+ return unix .RTPROT_KERNEL , nil
148
+ case "mrouted" :
149
+ return unix .RTPROT_MROUTED , nil
150
+ case "mrt" :
151
+ return unix .RTPROT_MRT , nil
152
+ case "ntk" :
153
+ return unix .RTPROT_NTK , nil
154
+ case "ospf" :
155
+ return unix .RTPROT_OSPF , nil
156
+ case "ra" :
157
+ return unix .RTPROT_RA , nil
158
+ case "redirect" :
159
+ return unix .RTPROT_REDIRECT , nil
160
+ case "rip" :
161
+ return unix .RTPROT_RIP , nil
162
+ case "static" :
163
+ return unix .RTPROT_STATIC , nil
164
+ case "unspec" :
165
+ return unix .RTPROT_UNSPEC , nil
166
+ case "xorp" :
167
+ return unix .RTPROT_XORP , nil
168
+ case "zebra" :
169
+ return unix .RTPROT_ZEBRA , nil
170
+ default :
171
+ proto , err := strconv .Atoi (r .Proto )
172
+ if err != nil {
173
+ return 0 , err
174
+ }
175
+
176
+ return netlink .RouteProtocol (proto ), nil
177
+ }
178
+ }
44
179
45
- _ , err := subprocess .RunCommand ("ip" , cmd ... )
180
+ // Add adds new route.
181
+ func (r * Route ) Add () error {
182
+ route , err := r .netlinkRoute ()
46
183
if err != nil {
47
184
return err
48
185
}
49
186
50
- return nil
187
+ return netlink . RouteAdd ( route )
51
188
}
52
189
53
190
// Delete deletes routing table.
54
191
func (r * Route ) Delete () error {
55
- cmd := []string {r .Family , "route" , "delete" , r .Route , "dev" , r .DevName }
56
-
57
- if r .VRF != "" {
58
- cmd = append (cmd , "vrf" , r .VRF )
59
- } else if r .Table != "" {
60
- cmd = append (cmd , "table" , r .Table )
61
- }
62
-
63
- _ , err := subprocess .RunCommand ("ip" , cmd ... )
192
+ route , err := r .netlinkRoute ()
64
193
if err != nil {
65
194
return err
66
195
}
67
196
68
- return nil
197
+ return netlink . RouteDel ( route )
69
198
}
70
199
71
- // Flush flushes routing tables.
72
- func (r * Route ) Flush () error {
73
- cmd := []string {}
74
- if r .Family != "" {
75
- cmd = append (cmd , r .Family )
76
- }
200
+ func routeFilterMask (route * netlink.Route ) uint64 {
201
+ var filterMask uint64
77
202
78
- cmd = append (cmd , "route" , "flush" )
79
- if r .Route != "" {
80
- cmd = append (cmd , r .Route )
203
+ if route .Dst != nil {
204
+ filterMask |= netlink .RT_FILTER_DST
81
205
}
82
206
83
- if r . Via != "" {
84
- cmd = append ( cmd , "via" , r . Via )
207
+ if route . Gw != nil {
208
+ filterMask |= netlink . RT_FILTER_GW
85
209
}
86
210
87
- cmd = append (cmd , "dev" , r .DevName )
88
- if r .Proto != "" {
89
- cmd = append (cmd , "proto" , r .Proto )
211
+ if route .Protocol != 0 {
212
+ filterMask |= netlink .RT_FILTER_PROTOCOL
90
213
}
91
214
92
- if r . VRF != "" {
93
- cmd = append ( cmd , "vrf" , r . VRF )
215
+ if route . Table != 0 {
216
+ filterMask |= netlink . RT_FILTER_TABLE
94
217
}
95
218
96
- _ , err := subprocess .RunCommand ("ip" , cmd ... )
219
+ return filterMask
220
+ }
221
+
222
+ // Flush flushes routing tables.
223
+ func (r * Route ) Flush () error {
224
+ route , err := r .netlinkRoute ()
97
225
if err != nil {
98
226
return err
99
227
}
100
228
101
- return nil
102
- }
229
+ var iterErr error
103
230
104
- // Replace changes or adds new route.
105
- func (r * Route ) Replace (routes []string ) error {
106
- cmd := []string {r .Family , "route" , "replace" , "dev" , r .DevName , "proto" , r .Proto }
231
+ err = netlink .RouteListFilteredIter (route .Family , route , routeFilterMask (route ), func (route netlink.Route ) (cont bool ) {
232
+ err2 := netlink .RouteDel (& route )
233
+ if err2 != nil {
234
+ iterErr = err2
235
+ return false
236
+ }
107
237
108
- if r .VRF != "" {
109
- cmd = append (cmd , "vrf" , r .VRF )
110
- }
238
+ return true
239
+ })
111
240
112
- cmd = append (cmd , routes ... )
113
- _ , err := subprocess .RunCommand ("ip" , cmd ... )
114
241
if err != nil {
115
242
return err
116
243
}
117
244
245
+ if iterErr != nil {
246
+ return iterErr
247
+ }
248
+
118
249
return nil
119
250
}
120
251
121
- // Show lists routes.
122
- func (r * Route ) Show () ([]string , error ) {
123
- routes := []string {}
252
+ // Replace changes or adds new route.
253
+ func (r * Route ) Replace () error {
254
+ route , err := r .netlinkRoute ()
255
+ if err != nil {
256
+ return err
257
+ }
124
258
125
- cmd := []string {r .Family , "route" , "show" , "dev" , r .DevName , "proto" , r .Proto }
259
+ return netlink .RouteReplace (route )
260
+ }
126
261
127
- if r .VRF != "" {
128
- cmd = append (cmd , "vrf" , r .VRF )
262
+ // Show lists routes.
263
+ func (r * Route ) Show () ([]Route , error ) {
264
+ route , err := r .netlinkRoute ()
265
+ if err != nil {
266
+ return nil , err
129
267
}
130
268
131
- out , err := subprocess . RunCommand ( "ip" , cmd ... )
269
+ netlinkRoutes , err := netlink . RouteListFiltered ( route . Family , route , routeFilterMask ( route ) )
132
270
if err != nil {
133
- return routes , err
271
+ return nil , err
134
272
}
135
273
136
- for _ , line := range strings .Split (out , "\n " ) {
137
- line = strings .TrimSpace (line )
138
- if len (line ) == 0 {
139
- continue
274
+ routes := make ([]Route , 0 , len (netlinkRoutes ))
275
+
276
+ for _ , netlinkRoute := range netlinkRoutes {
277
+ var src , gw , table string
278
+
279
+ if len (netlinkRoute .Src ) > 0 {
280
+ src = netlinkRoute .Src .String ()
281
+ }
282
+
283
+ if len (netlinkRoute .Gw ) > 0 {
284
+ gw = netlinkRoute .Gw .String ()
285
+ }
286
+
287
+ switch netlinkRoute .Table {
288
+ case unix .RT_TABLE_MAIN :
289
+ table = "main"
290
+ case unix .RT_TABLE_LOCAL :
291
+ table = "local"
292
+ case unix .RT_TABLE_DEFAULT :
293
+ table = "default"
294
+ default :
295
+ table = strconv .Itoa (netlinkRoute .Table )
140
296
}
141
297
142
- route := strings .ReplaceAll (line , "linkdown" , "" )
143
- routes = append (routes , route )
298
+ routes = append (routes , Route {
299
+ DevName : r .DevName ,
300
+ Route : netlinkRoute .Dst .String (),
301
+ Src : src ,
302
+ Via : gw ,
303
+ Table : table ,
304
+ VRF : "" , // adding a route to a VRF just adds it to the table associated with the VRF, so when retrieving routes that information is not available anymore and we just set the table
305
+ Proto : netlinkRoute .Protocol .String (),
306
+ Family : r .Family , // routes are filtered by family
307
+ })
144
308
}
145
309
146
310
return routes , nil
0 commit comments