Skip to content

Commit 4cafcdd

Browse files
committed
feat: convert execs to ip to netlink calls
Not making direct exec calls to user binary interfaces has long been a principle of kube-router. When kube-router was first coded, the netlink library was missing significant features that forced us to exec out. However, now netlink seems to have most of the functionality that we need. This converts all of the places where we can use netlink to use the netlink functionality.
1 parent 81c4a27 commit 4cafcdd

File tree

6 files changed

+206
-110
lines changed

6 files changed

+206
-110
lines changed

pkg/controllers/proxy/linux_networking.go

Lines changed: 135 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"fmt"
66
"net"
77
"os"
8-
"os/exec"
98
"path"
109
"strconv"
1110
"strings"
@@ -24,8 +23,10 @@ import (
2423
)
2524

2625
const (
27-
ipv4NetMaskBits = 32
28-
ipv6NetMaskBits = 128
26+
ipv4NetMaskBits = 32
27+
ipv4DefaultRoute = "0.0.0.0/0"
28+
ipv6NetMaskBits = 128
29+
ipv6DefaultRoute = "::/0"
2930

3031
// TODO: it's bad to rely on eth0 here. While this is inside the container's namespace and is determined by the
3132
// container runtime and so far we've been able to count on this being reliably set to eth0, it is possible that
@@ -67,7 +68,6 @@ type netlinkCalls interface {
6768

6869
func (ln *linuxNetworking) ipAddrDel(iface netlink.Link, ip string, nodeIP string) error {
6970
var netMask net.IPMask
70-
var ipRouteCmdArgs []string
7171
parsedIP := net.ParseIP(ip)
7272
parsedNodeIP := net.ParseIP(nodeIP)
7373
if parsedIP.To4() != nil {
@@ -77,7 +77,6 @@ func (ln *linuxNetworking) ipAddrDel(iface netlink.Link, ip string, nodeIP strin
7777
}
7878

7979
netMask = net.CIDRMask(ipv4NetMaskBits, ipv4NetMaskBits)
80-
ipRouteCmdArgs = make([]string, 0)
8180
} else {
8281
// If the IP family of the NodeIP and the VIP IP don't match, we can't proceed
8382
if parsedNodeIP.To4() != nil {
@@ -90,7 +89,6 @@ func (ln *linuxNetworking) ipAddrDel(iface netlink.Link, ip string, nodeIP strin
9089
}
9190

9291
netMask = net.CIDRMask(ipv6NetMaskBits, ipv6NetMaskBits)
93-
ipRouteCmdArgs = []string{"-6"}
9492
}
9593

9694
naddr := &netlink.Addr{IPNet: &net.IPNet{IP: parsedIP, Mask: netMask}, Scope: syscall.RT_SCOPE_LINK}
@@ -108,13 +106,20 @@ func (ln *linuxNetworking) ipAddrDel(iface netlink.Link, ip string, nodeIP strin
108106

109107
// Delete VIP addition to "local" rt table also, fail silently if not found (DSR special case)
110108
// #nosec G204
111-
ipRouteCmdArgs = append(ipRouteCmdArgs, "route", "delete", "local", ip, "dev", KubeDummyIf,
112-
"table", "local", "proto", "kernel", "scope", "host", "src", nodeIP, "table", "local")
113-
out, err := exec.Command("ip", ipRouteCmdArgs...).CombinedOutput()
109+
nRoute := &netlink.Route{
110+
Type: unix.RTN_LOCAL,
111+
Dst: &net.IPNet{IP: parsedIP, Mask: netMask},
112+
LinkIndex: iface.Attrs().Index,
113+
Table: syscall.RT_TABLE_LOCAL,
114+
Protocol: unix.RTPROT_KERNEL,
115+
Scope: syscall.RT_SCOPE_HOST,
116+
Src: parsedNodeIP,
117+
}
118+
err = netlink.RouteDel(nRoute)
114119
if err != nil {
115-
if !strings.Contains(string(out), "No such process") {
116-
klog.Errorf("Failed to delete route to service VIP %s configured on %s. Error: %v, Output: %s",
117-
ip, KubeDummyIf, err, out)
120+
if !strings.Contains(err.Error(), "no such process") {
121+
klog.Errorf("Failed to delete route to service VIP %s configured on %s. Error: %v",
122+
ip, iface.Attrs().Name, err)
118123
} else {
119124
klog.Warningf("got a No such process error while trying to remove route: %v (this is not normally bad "+
120125
"enough to stop processing)", err)
@@ -130,7 +135,6 @@ func (ln *linuxNetworking) ipAddrDel(iface netlink.Link, ip string, nodeIP strin
130135
// inside the container.
131136
func (ln *linuxNetworking) ipAddrAdd(iface netlink.Link, ip string, nodeIP string, addRoute bool) error {
132137
var netMask net.IPMask
133-
var ipRouteCmdArgs []string
134138
var isIPv6 bool
135139
parsedIP := net.ParseIP(ip)
136140
parsedNodeIP := net.ParseIP(nodeIP)
@@ -141,7 +145,6 @@ func (ln *linuxNetworking) ipAddrAdd(iface netlink.Link, ip string, nodeIP strin
141145
}
142146

143147
netMask = net.CIDRMask(ipv4NetMaskBits, ipv4NetMaskBits)
144-
ipRouteCmdArgs = make([]string, 0)
145148
isIPv6 = false
146149
} else {
147150
// If we're supposed to add a route and the IP family of the NodeIP and the VIP IP don't match, we can't proceed
@@ -150,11 +153,11 @@ func (ln *linuxNetworking) ipAddrAdd(iface netlink.Link, ip string, nodeIP strin
150153
}
151154

152155
netMask = net.CIDRMask(ipv6NetMaskBits, ipv6NetMaskBits)
153-
ipRouteCmdArgs = []string{"-6"}
154156
isIPv6 = true
155157
}
156158

157-
naddr := &netlink.Addr{IPNet: &net.IPNet{IP: parsedIP, Mask: netMask}, Scope: syscall.RT_SCOPE_LINK}
159+
ipPrefix := &net.IPNet{IP: parsedIP, Mask: netMask}
160+
naddr := &netlink.Addr{IPNet: ipPrefix, Scope: syscall.RT_SCOPE_LINK}
158161
err := netlink.AddrAdd(iface, naddr)
159162
if err != nil && err.Error() != IfaceHasAddr {
160163
klog.Errorf("failed to assign cluster ip %s to dummy interface: %s", naddr.IP.String(), err.Error())
@@ -169,16 +172,24 @@ func (ln *linuxNetworking) ipAddrAdd(iface netlink.Link, ip string, nodeIP strin
169172
return nil
170173
}
171174

172-
// TODO: netlink.RouteReplace which is replacement for below command is not working as expected. Call succeeds but
173-
// route is not replaced. For now do it with command.
174-
// #nosec G204
175-
ipRouteCmdArgs = append(ipRouteCmdArgs, "route", "replace", "local", ip, "dev", KubeDummyIf,
176-
"table", "local", "proto", "kernel", "scope", "host", "src", nodeIP, "table", "local")
177-
178-
out, err := exec.Command("ip", ipRouteCmdArgs...).CombinedOutput()
175+
kubeDummyLink, err := netlink.LinkByName(KubeDummyIf)
179176
if err != nil {
180-
klog.Errorf("Failed to replace route to service VIP %s configured on %s. Error: %v, Output: %s",
181-
ip, KubeDummyIf, err, out)
177+
klog.Errorf("failed to get %s link due to %v", KubeDummyIf, err)
178+
return err
179+
}
180+
nRoute := &netlink.Route{
181+
Type: unix.RTN_LOCAL,
182+
Dst: ipPrefix,
183+
LinkIndex: kubeDummyLink.Attrs().Index,
184+
Table: syscall.RT_TABLE_LOCAL,
185+
Protocol: unix.RTPROT_KERNEL,
186+
Scope: syscall.RT_SCOPE_HOST,
187+
Src: parsedNodeIP,
188+
}
189+
err = netlink.RouteReplace(nRoute)
190+
if err != nil {
191+
klog.Errorf("Failed to replace route to service VIP %s configured on %s. Error: %v",
192+
ip, KubeDummyIf, err)
182193
return err
183194
}
184195

@@ -482,60 +493,108 @@ func (ln *linuxNetworking) setupPolicyRoutingForDSR(setupIPv4, setupIPv6 bool) e
482493
return fmt.Errorf("failed to setup policy routing required for DSR due to %v", err)
483494
}
484495

496+
loNetLink, err := netlink.LinkByName("lo")
497+
if err != nil {
498+
return fmt.Errorf("failed to get loopback interface due to %v", err)
499+
}
500+
485501
if setupIPv4 {
486-
out, err := exec.Command("ip", "route", "list", "table", customDSRRouteTableID).Output()
487-
if err != nil || !strings.Contains(string(out), " lo ") {
488-
if err = exec.Command("ip", "route", "add", "local", "default", "dev", "lo", "table",
489-
customDSRRouteTableID).Run(); err != nil {
490-
return fmt.Errorf("failed to add route in custom route table due to: %v", err)
502+
nFamily := netlink.FAMILY_V4
503+
_, defaultRouteCIDR, err := net.ParseCIDR(ipv4DefaultRoute)
504+
if err != nil {
505+
return fmt.Errorf("failed to parse default (%s) route (this is statically defined, so if you see this "+
506+
"error please report because something has gone very wrong) due to: %v", ipv4DefaultRoute, err)
507+
}
508+
nRoute := &netlink.Route{
509+
Type: unix.RTN_LOCAL,
510+
Dst: defaultRouteCIDR,
511+
LinkIndex: loNetLink.Attrs().Index,
512+
Table: customDSRRouteTableID,
513+
}
514+
routes, err := netlink.RouteListFiltered(nFamily, nRoute, netlink.RT_FILTER_TABLE|netlink.RT_FILTER_OIF)
515+
if err != nil || len(routes) < 1 {
516+
err = netlink.RouteAdd(nRoute)
517+
if err != nil {
518+
return fmt.Errorf("failed to add route to custom route table for DSR due to: %v", err)
491519
}
492520
}
493521
}
522+
494523
if setupIPv6 {
495-
out, err := exec.Command("ip", "-6", "route", "list", "table", customDSRRouteTableID).Output()
496-
if err != nil || !strings.Contains(string(out), " lo ") {
497-
if err = exec.Command("ip", "-6", "route", "add", "local", "default", "dev", "lo", "table",
498-
customDSRRouteTableID).Run(); err != nil {
499-
return fmt.Errorf("failed to add route in custom route table due to: %v", err)
524+
nFamily := netlink.FAMILY_V6
525+
_, defaultRouteCIDR, err := net.ParseCIDR(ipv6DefaultRoute)
526+
if err != nil {
527+
return fmt.Errorf("failed to parse default (%s) route (this is statically defined, so if you see this "+
528+
"error please report because something has gone very wrong) due to: %v", ipv6DefaultRoute, err)
529+
}
530+
nRoute := &netlink.Route{
531+
Type: unix.RTN_LOCAL,
532+
Dst: defaultRouteCIDR,
533+
LinkIndex: loNetLink.Attrs().Index,
534+
Table: customDSRRouteTableID,
535+
}
536+
routes, err := netlink.RouteListFiltered(nFamily, nRoute, netlink.RT_FILTER_TABLE|netlink.RT_FILTER_OIF)
537+
if err != nil || len(routes) < 1 {
538+
err = netlink.RouteAdd(nRoute)
539+
if err != nil {
540+
return fmt.Errorf("failed to add route to custom route table for DSR due to: %v", err)
500541
}
501542
}
502543
}
544+
503545
return nil
504546
}
505547

506548
// For DSR it is required that node needs to know how to route external IP. Otherwise when endpoint
507549
// directly responds back with source IP as external IP kernel will treat as martian packet.
508550
// To prevent martian packets add route to external IP through the `kube-bridge` interface
509551
// setupRoutesForExternalIPForDSR: setups routing so that kernel does not think return packets as martians
510-
511552
func (ln *linuxNetworking) setupRoutesForExternalIPForDSR(serviceInfoMap serviceInfoMap,
512553
setupIPv4, setupIPv6 bool) error {
513554
err := utils.RouteTableAdd(externalIPRouteTableID, externalIPRouteTableName)
514555
if err != nil {
515556
return fmt.Errorf("failed to setup policy routing required for DSR due to %v", err)
516557
}
517558

518-
setupIPRulesAndRoutes := func(ipArgs []string) error {
519-
out, err := runIPCommandsWithArgs(ipArgs, "rule", "list").Output()
559+
setupIPRulesAndRoutes := func(isIPv6 bool) error {
560+
nFamily := netlink.FAMILY_V4
561+
_, defaultPrefixCIDR, err := net.ParseCIDR(ipv4DefaultRoute)
562+
if isIPv6 {
563+
nFamily = netlink.FAMILY_V6
564+
_, defaultPrefixCIDR, err = net.ParseCIDR(ipv6DefaultRoute)
565+
}
566+
if err != nil {
567+
return fmt.Errorf("failed to parse default route (this is statically defined, so if you see this "+
568+
"error please report because something has gone very wrong) due to: %v", err)
569+
}
570+
571+
nRule := &netlink.Rule{
572+
Priority: defaultDSRPolicyRulePriority,
573+
Src: defaultPrefixCIDR,
574+
Table: externalIPRouteTableID,
575+
}
576+
rules, err := netlink.RuleListFiltered(nFamily, nRule,
577+
netlink.RT_FILTER_TABLE|netlink.RT_FILTER_SRC|netlink.RT_FILTER_PRIORITY)
520578
if err != nil {
521-
return fmt.Errorf("failed to verify if `ip rule add prio 32765 from all lookup external_ip` exists due to: %v",
522-
err)
579+
return fmt.Errorf("failed to list rule for external IP's and verify if `ip rule add prio 32765 from all "+
580+
"lookup external_ip` exists due to: %v", err)
523581
}
524582

525-
if !strings.Contains(string(out), externalIPRouteTableName) &&
526-
!strings.Contains(string(out), externalIPRouteTableID) {
527-
err = runIPCommandsWithArgs(ipArgs, "rule", "add", "prio", "32765", "from", "all", "lookup",
528-
externalIPRouteTableID).Run()
583+
if len(rules) < 1 {
584+
err = netlink.RuleAdd(nRule)
529585
if err != nil {
530586
klog.Infof("Failed to add policy rule `ip rule add prio 32765 from all lookup external_ip` due to %v",
531-
err.Error())
587+
err)
532588
return fmt.Errorf("failed to add policy rule `ip rule add prio 32765 from all lookup external_ip` "+
533589
"due to %v", err)
534590
}
535591
}
536592

537-
out, _ = runIPCommandsWithArgs(ipArgs, "route", "list", "table", externalIPRouteTableID).Output()
538-
outStr := string(out)
593+
kubeBridgeLink, err := netlink.LinkByName(KubeBridgeIf)
594+
if err != nil {
595+
return fmt.Errorf("failed to get kube-bridge interface due to %v", err)
596+
}
597+
539598
activeExternalIPs := make(map[string]bool)
540599
for _, svc := range serviceInfoMap {
541600
for _, externalIP := range svc.externalIPs {
@@ -548,9 +607,21 @@ func (ln *linuxNetworking) setupRoutesForExternalIPForDSR(serviceInfoMap service
548607

549608
activeExternalIPs[externalIP] = true
550609

551-
if !strings.Contains(outStr, externalIP) {
552-
if err = runIPCommandsWithArgs(ipArgs, "route", "add", externalIP, "dev", "kube-bridge", "table",
553-
externalIPRouteTableID).Run(); err != nil {
610+
nSrcIP := net.ParseIP(externalIP)
611+
nRoute := &netlink.Route{
612+
Src: nSrcIP,
613+
LinkIndex: kubeBridgeLink.Attrs().Index,
614+
Table: externalIPRouteTableID,
615+
}
616+
617+
routes, err := netlink.RouteListFiltered(nFamily, nRoute,
618+
netlink.RT_FILTER_SRC|netlink.RT_FILTER_TABLE|netlink.RT_FILTER_OIF)
619+
if err != nil {
620+
return fmt.Errorf("failed to list route for external IP's due to: %s", err)
621+
}
622+
if len(routes) < 1 {
623+
err = netlink.RouteAdd(nRoute)
624+
if err != nil {
554625
klog.Errorf("Failed to add route for %s in custom route table for external IP's due to: %v",
555626
externalIP, err)
556627
continue
@@ -560,19 +631,18 @@ func (ln *linuxNetworking) setupRoutesForExternalIPForDSR(serviceInfoMap service
560631
}
561632

562633
// check if there are any pbr in externalIPRouteTableID for external IP's
563-
if len(outStr) > 0 {
564-
// clean up stale external IPs
565-
for _, line := range strings.Split(strings.Trim(outStr, "\n"), "\n") {
566-
route := strings.Split(strings.Trim(line, " "), " ")
567-
ip := route[0]
568-
if !activeExternalIPs[ip] {
569-
args := []string{"route", "del", "table", externalIPRouteTableID}
570-
args = append(args, route...)
571-
if err = runIPCommandsWithArgs(ipArgs, args...).Run(); err != nil {
572-
klog.Errorf("Failed to del route for %v in custom route table for external IP's due to: %s",
573-
ip, err)
574-
continue
575-
}
634+
routes, err := netlink.RouteList(nil, nFamily)
635+
if err != nil {
636+
return fmt.Errorf("failed to list route for external IP's due to: %s", err)
637+
}
638+
for idx, route := range routes {
639+
ip := route.Src.String()
640+
if !activeExternalIPs[ip] {
641+
err = netlink.RouteDel(&routes[idx])
642+
if err != nil {
643+
klog.Errorf("Failed to del route for %v in custom route table for external IP's due to: %s",
644+
ip, err)
645+
continue
576646
}
577647
}
578648
}
@@ -581,13 +651,13 @@ func (ln *linuxNetworking) setupRoutesForExternalIPForDSR(serviceInfoMap service
581651
}
582652

583653
if setupIPv4 {
584-
err = setupIPRulesAndRoutes([]string{})
654+
err = setupIPRulesAndRoutes(false)
585655
if err != nil {
586656
return err
587657
}
588658
}
589659
if setupIPv6 {
590-
err = setupIPRulesAndRoutes([]string{"-6"})
660+
err = setupIPRulesAndRoutes(true)
591661
if err != nil {
592662
return err
593663
}

0 commit comments

Comments
 (0)