Skip to content

Commit 8d1945d

Browse files
committed
incusd/ip/route: Switch to netlink
Signed-off-by: Gwendolyn <[email protected]>
1 parent 5661420 commit 8d1945d

File tree

2 files changed

+235
-83
lines changed

2 files changed

+235
-83
lines changed

internal/server/ip/route.go

+229-65
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package ip
22

33
import (
4-
"strings"
4+
"fmt"
5+
"net"
6+
"strconv"
57

6-
"github.com/lxc/incus/v6/shared/subprocess"
8+
"github.com/vishvananda/netlink"
9+
"golang.org/x/sys/unix"
710
)
811

912
// Route represents arguments for route manipulation.
@@ -18,129 +21,290 @@ type Route struct {
1821
VRF string
1922
}
2023

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+
2449
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)
2668
}
2769

2870
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
3077
}
3178

32-
cmd = append(cmd, r.Route, "dev", r.DevName)
3379
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
3586
}
3687

3788
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
3995
}
4096

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)
43110
}
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+
}
44179

45-
_, err := subprocess.RunCommand("ip", cmd...)
180+
// Add adds new route.
181+
func (r *Route) Add() error {
182+
route, err := r.netlinkRoute()
46183
if err != nil {
47184
return err
48185
}
49186

50-
return nil
187+
return netlink.RouteAdd(route)
51188
}
52189

53190
// Delete deletes routing table.
54191
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()
64193
if err != nil {
65194
return err
66195
}
67196

68-
return nil
197+
return netlink.RouteDel(route)
69198
}
70199

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
77202

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
81205
}
82206

83-
if r.Via != "" {
84-
cmd = append(cmd, "via", r.Via)
207+
if route.Gw != nil {
208+
filterMask |= netlink.RT_FILTER_GW
85209
}
86210

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
90213
}
91214

92-
if r.VRF != "" {
93-
cmd = append(cmd, "vrf", r.VRF)
215+
if route.Table != 0 {
216+
filterMask |= netlink.RT_FILTER_TABLE
94217
}
95218

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()
97225
if err != nil {
98226
return err
99227
}
100228

101-
return nil
102-
}
229+
var iterErr error
103230

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+
}
107237

108-
if r.VRF != "" {
109-
cmd = append(cmd, "vrf", r.VRF)
110-
}
238+
return true
239+
})
111240

112-
cmd = append(cmd, routes...)
113-
_, err := subprocess.RunCommand("ip", cmd...)
114241
if err != nil {
115242
return err
116243
}
117244

245+
if iterErr != nil {
246+
return iterErr
247+
}
248+
118249
return nil
119250
}
120251

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+
}
124258

125-
cmd := []string{r.Family, "route", "show", "dev", r.DevName, "proto", r.Proto}
259+
return netlink.RouteReplace(route)
260+
}
126261

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
129267
}
130268

131-
out, err := subprocess.RunCommand("ip", cmd...)
269+
netlinkRoutes, err := netlink.RouteListFiltered(route.Family, route, routeFilterMask(route))
132270
if err != nil {
133-
return routes, err
271+
return nil, err
134272
}
135273

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)
140296
}
141297

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+
})
144308
}
145309

146310
return routes, nil

0 commit comments

Comments
 (0)