2019-04-29 21:25:05 +00:00
|
|
|
// Copyright 2018 The gVisor Authors.
|
2018-04-27 17:37:02 +00:00
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
package sandbox
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"net"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"runtime"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"syscall"
|
|
|
|
|
|
|
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
|
|
|
"github.com/vishvananda/netlink"
|
2019-03-29 23:26:36 +00:00
|
|
|
"golang.org/x/sys/unix"
|
2019-06-13 23:49:09 +00:00
|
|
|
"gvisor.dev/gvisor/pkg/log"
|
2019-10-22 18:54:14 +00:00
|
|
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
2019-06-13 23:49:09 +00:00
|
|
|
"gvisor.dev/gvisor/pkg/urpc"
|
|
|
|
"gvisor.dev/gvisor/runsc/boot"
|
|
|
|
"gvisor.dev/gvisor/runsc/specutils"
|
2018-04-27 17:37:02 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// setupNetwork configures the network stack to mimic the local network
|
|
|
|
// configuration. Docker uses network namespaces with vnets to configure the
|
|
|
|
// network for the container. The untrusted app expects to see the same network
|
|
|
|
// inside the sandbox. Routing and port mapping is handled directly by docker
|
|
|
|
// with most of network information not even available to the runtime.
|
|
|
|
//
|
|
|
|
// Netstack inside the sandbox speaks directly to the device using a raw socket.
|
|
|
|
// All IP addresses assigned to the NIC, are removed and passed on to netstack's
|
|
|
|
// device.
|
|
|
|
//
|
|
|
|
// If 'conf.Network' is NoNetwork, skips local configuration and creates a
|
|
|
|
// loopback interface only.
|
|
|
|
//
|
|
|
|
// Run the following container to test it:
|
|
|
|
// docker run -di --runtime=runsc -p 8080:80 -v $PWD:/usr/local/apache2/htdocs/ httpd:2.4
|
|
|
|
func setupNetwork(conn *urpc.Client, pid int, spec *specs.Spec, conf *boot.Config) error {
|
|
|
|
log.Infof("Setting up network")
|
|
|
|
|
|
|
|
switch conf.Network {
|
|
|
|
case boot.NetworkNone:
|
|
|
|
log.Infof("Network is disabled, create loopback interface only")
|
|
|
|
if err := createDefaultLoopbackInterface(conn); err != nil {
|
2019-01-19 01:35:09 +00:00
|
|
|
return fmt.Errorf("creating default loopback interface: %v", err)
|
2018-04-27 17:37:02 +00:00
|
|
|
}
|
|
|
|
case boot.NetworkSandbox:
|
|
|
|
// Build the path to the net namespace of the sandbox process.
|
|
|
|
// This is what we will copy.
|
|
|
|
nsPath := filepath.Join("/proc", strconv.Itoa(pid), "ns/net")
|
2019-10-22 18:54:14 +00:00
|
|
|
if err := createInterfacesAndRoutesFromNS(conn, nsPath, conf.HardwareGSO, conf.SoftwareGSO, conf.NumNetworkChannels); err != nil {
|
2019-01-19 01:35:09 +00:00
|
|
|
return fmt.Errorf("creating interfaces from net namespace %q: %v", nsPath, err)
|
2018-04-27 17:37:02 +00:00
|
|
|
}
|
|
|
|
case boot.NetworkHost:
|
|
|
|
// Nothing to do here.
|
|
|
|
default:
|
2019-01-19 01:35:09 +00:00
|
|
|
return fmt.Errorf("invalid network type: %d", conf.Network)
|
2018-04-27 17:37:02 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func createDefaultLoopbackInterface(conn *urpc.Client) error {
|
|
|
|
link := boot.LoopbackLink{
|
|
|
|
Name: "lo",
|
|
|
|
Addresses: []net.IP{
|
|
|
|
net.IP("\x7f\x00\x00\x01"),
|
|
|
|
net.IPv6loopback,
|
|
|
|
},
|
|
|
|
Routes: []boot.Route{
|
|
|
|
{
|
2019-08-21 22:30:13 +00:00
|
|
|
Destination: net.IPNet{
|
|
|
|
|
|
|
|
IP: net.IPv4(0x7f, 0, 0, 0),
|
|
|
|
Mask: net.IPv4Mask(0xff, 0, 0, 0),
|
|
|
|
},
|
2018-04-27 17:37:02 +00:00
|
|
|
},
|
|
|
|
{
|
2019-08-21 22:30:13 +00:00
|
|
|
Destination: net.IPNet{
|
|
|
|
IP: net.IPv6loopback,
|
|
|
|
Mask: net.IPMask(strings.Repeat("\xff", net.IPv6len)),
|
|
|
|
},
|
2018-04-27 17:37:02 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
if err := conn.Call(boot.NetworkCreateLinksAndRoutes, &boot.CreateLinksAndRoutesArgs{
|
|
|
|
LoopbackLinks: []boot.LoopbackLink{link},
|
|
|
|
}, nil); err != nil {
|
2019-01-19 01:35:09 +00:00
|
|
|
return fmt.Errorf("creating loopback link and routes: %v", err)
|
2018-04-27 17:37:02 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func joinNetNS(nsPath string) (func(), error) {
|
|
|
|
runtime.LockOSThread()
|
2018-08-27 18:09:06 +00:00
|
|
|
restoreNS, err := specutils.ApplyNS(specs.LinuxNamespace{
|
2018-04-27 17:37:02 +00:00
|
|
|
Type: specs.NetworkNamespace,
|
|
|
|
Path: nsPath,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
runtime.UnlockOSThread()
|
2019-01-19 01:35:09 +00:00
|
|
|
return nil, fmt.Errorf("joining net namespace %q: %v", nsPath, err)
|
2018-04-27 17:37:02 +00:00
|
|
|
}
|
|
|
|
return func() {
|
|
|
|
restoreNS()
|
|
|
|
runtime.UnlockOSThread()
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// isRootNS determines whether we are running in the root net namespace.
|
2019-01-29 01:19:18 +00:00
|
|
|
// /proc/sys/net/core/rmem_default only exists in root network namespace.
|
|
|
|
func isRootNS() (bool, error) {
|
|
|
|
err := syscall.Access("/proc/sys/net/core/rmem_default", syscall.F_OK)
|
|
|
|
switch err {
|
|
|
|
case nil:
|
|
|
|
return true, nil
|
|
|
|
case syscall.ENOENT:
|
|
|
|
return false, nil
|
|
|
|
default:
|
|
|
|
return false, fmt.Errorf("failed to access /proc/sys/net/core/rmem_default: %v", err)
|
2018-04-27 17:37:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// createInterfacesAndRoutesFromNS scrapes the interface and routes from the
|
|
|
|
// net namespace with the given path, creates them in the sandbox, and removes
|
|
|
|
// them from the host.
|
2019-10-22 18:54:14 +00:00
|
|
|
func createInterfacesAndRoutesFromNS(conn *urpc.Client, nsPath string, hardwareGSO bool, softwareGSO bool, numNetworkChannels int) error {
|
2018-04-27 17:37:02 +00:00
|
|
|
// Join the network namespace that we will be copying.
|
|
|
|
restore, err := joinNetNS(nsPath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer restore()
|
|
|
|
|
|
|
|
// Get all interfaces in the namespace.
|
|
|
|
ifaces, err := net.Interfaces()
|
|
|
|
if err != nil {
|
2019-01-19 01:35:09 +00:00
|
|
|
return fmt.Errorf("querying interfaces: %v", err)
|
2018-04-27 17:37:02 +00:00
|
|
|
}
|
|
|
|
|
2019-01-29 01:19:18 +00:00
|
|
|
isRoot, err := isRootNS()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if isRoot {
|
|
|
|
|
|
|
|
return fmt.Errorf("cannot run with network enabled in root network namespace")
|
2018-04-27 17:37:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Collect addresses and routes from the interfaces.
|
|
|
|
var args boot.CreateLinksAndRoutesArgs
|
|
|
|
for _, iface := range ifaces {
|
|
|
|
if iface.Flags&net.FlagUp == 0 {
|
|
|
|
log.Infof("Skipping down interface: %+v", iface)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2018-06-01 17:08:40 +00:00
|
|
|
allAddrs, err := iface.Addrs()
|
2018-04-27 17:37:02 +00:00
|
|
|
if err != nil {
|
2019-01-19 01:35:09 +00:00
|
|
|
return fmt.Errorf("fetching interface addresses for %q: %v", iface.Name, err)
|
2018-04-27 17:37:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// We build our own loopback devices.
|
|
|
|
if iface.Flags&net.FlagLoopback != 0 {
|
2018-06-01 17:08:40 +00:00
|
|
|
links, err := loopbackLinks(iface, allAddrs)
|
2018-04-27 17:37:02 +00:00
|
|
|
if err != nil {
|
2019-01-19 01:35:09 +00:00
|
|
|
return fmt.Errorf("getting loopback routes and links for iface %q: %v", iface.Name, err)
|
2018-04-27 17:37:02 +00:00
|
|
|
}
|
|
|
|
args.LoopbackLinks = append(args.LoopbackLinks, links...)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2018-06-01 17:08:40 +00:00
|
|
|
// Keep only IPv4 addresses.
|
|
|
|
var ip4addrs []*net.IPNet
|
|
|
|
for _, ifaddr := range allAddrs {
|
|
|
|
ipNet, ok := ifaddr.(*net.IPNet)
|
|
|
|
if !ok {
|
|
|
|
return fmt.Errorf("address is not IPNet: %+v", ifaddr)
|
|
|
|
}
|
|
|
|
if ipNet.IP.To4() == nil {
|
|
|
|
log.Warningf("IPv6 is not supported, skipping: %v", ipNet)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
ip4addrs = append(ip4addrs, ipNet)
|
|
|
|
}
|
|
|
|
if len(ip4addrs) == 0 {
|
|
|
|
log.Warningf("No IPv4 address found for interface %q, skipping", iface.Name)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2018-04-27 17:37:02 +00:00
|
|
|
// Scrape the routes before removing the address, since that
|
|
|
|
// will remove the routes as well.
|
|
|
|
routes, def, err := routesForIface(iface)
|
|
|
|
if err != nil {
|
2019-01-19 01:35:09 +00:00
|
|
|
return fmt.Errorf("getting routes for interface %q: %v", iface.Name, err)
|
2018-04-27 17:37:02 +00:00
|
|
|
}
|
|
|
|
if def != nil {
|
|
|
|
if !args.DefaultGateway.Route.Empty() {
|
|
|
|
return fmt.Errorf("more than one default route found, interface: %v, route: %v, default route: %+v", iface.Name, def, args.DefaultGateway)
|
|
|
|
}
|
|
|
|
args.DefaultGateway.Route = *def
|
|
|
|
args.DefaultGateway.Name = iface.Name
|
|
|
|
}
|
|
|
|
|
|
|
|
link := boot.FDBasedLink{
|
2019-06-06 15:05:46 +00:00
|
|
|
Name: iface.Name,
|
|
|
|
MTU: iface.MTU,
|
|
|
|
Routes: routes,
|
|
|
|
NumChannels: numNetworkChannels,
|
2018-04-27 17:37:02 +00:00
|
|
|
}
|
|
|
|
|
2018-08-06 18:47:07 +00:00
|
|
|
// Get the link for the interface.
|
|
|
|
ifaceLink, err := netlink.LinkByName(iface.Name)
|
|
|
|
if err != nil {
|
2019-01-19 01:35:09 +00:00
|
|
|
return fmt.Errorf("getting link for interface %q: %v", iface.Name, err)
|
2018-08-06 18:47:07 +00:00
|
|
|
}
|
2019-06-11 21:30:34 +00:00
|
|
|
link.LinkAddress = ifaceLink.Attrs().HardwareAddr
|
2018-08-06 18:47:07 +00:00
|
|
|
|
2019-06-06 15:05:46 +00:00
|
|
|
log.Debugf("Setting up network channels")
|
|
|
|
// Create the socket for the device.
|
|
|
|
for i := 0; i < link.NumChannels; i++ {
|
|
|
|
log.Debugf("Creating Channel %d", i)
|
2019-10-22 18:54:14 +00:00
|
|
|
socketEntry, err := createSocket(iface, ifaceLink, hardwareGSO)
|
2019-03-29 23:26:36 +00:00
|
|
|
if err != nil {
|
2019-06-06 15:05:46 +00:00
|
|
|
return fmt.Errorf("failed to createSocket for %s : %v", iface.Name, err)
|
2019-03-29 23:26:36 +00:00
|
|
|
}
|
2019-06-06 15:05:46 +00:00
|
|
|
if i == 0 {
|
|
|
|
link.GSOMaxSize = socketEntry.gsoMaxSize
|
2019-04-23 23:10:05 +00:00
|
|
|
} else {
|
2019-06-06 15:05:46 +00:00
|
|
|
if link.GSOMaxSize != socketEntry.gsoMaxSize {
|
|
|
|
return fmt.Errorf("inconsistent gsoMaxSize %d and %d when creating multiple channels for same interface: %s",
|
|
|
|
link.GSOMaxSize, socketEntry.gsoMaxSize, iface.Name)
|
|
|
|
}
|
2019-03-29 23:26:36 +00:00
|
|
|
}
|
2019-06-06 15:05:46 +00:00
|
|
|
args.FilePayload.Files = append(args.FilePayload.Files, socketEntry.deviceFile)
|
2019-04-26 19:50:38 +00:00
|
|
|
}
|
2019-10-22 18:54:14 +00:00
|
|
|
if link.GSOMaxSize == 0 && softwareGSO {
|
|
|
|
// Hardware GSO is disabled. Let's enable software GSO.
|
|
|
|
link.GSOMaxSize = stack.SoftwareGSOMaxSize
|
|
|
|
link.SoftwareGSOEnabled = true
|
|
|
|
}
|
2019-04-26 19:50:38 +00:00
|
|
|
|
2018-04-27 17:37:02 +00:00
|
|
|
// Collect the addresses for the interface, enable forwarding,
|
|
|
|
// and remove them from the host.
|
2018-06-01 17:08:40 +00:00
|
|
|
for _, addr := range ip4addrs {
|
|
|
|
link.Addresses = append(link.Addresses, addr.IP)
|
2018-04-27 17:37:02 +00:00
|
|
|
|
|
|
|
// Steal IP address from NIC.
|
2018-06-01 17:08:40 +00:00
|
|
|
if err := removeAddress(ifaceLink, addr.String()); err != nil {
|
2019-01-19 01:35:09 +00:00
|
|
|
return fmt.Errorf("removing address %v from device %q: %v", iface.Name, addr, err)
|
2018-04-27 17:37:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
args.FDBasedLinks = append(args.FDBasedLinks, link)
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Debugf("Setting up network, config: %+v", args)
|
|
|
|
if err := conn.Call(boot.NetworkCreateLinksAndRoutes, &args, nil); err != nil {
|
2019-01-19 01:35:09 +00:00
|
|
|
return fmt.Errorf("creating links and routes: %v", err)
|
2018-04-27 17:37:02 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-06-06 15:05:46 +00:00
|
|
|
type socketEntry struct {
|
|
|
|
deviceFile *os.File
|
|
|
|
gsoMaxSize uint32
|
|
|
|
}
|
|
|
|
|
|
|
|
// createSocket creates an underlying AF_PACKET socket and configures it for use by
|
|
|
|
// the sentry and returns an *os.File that wraps the underlying socket fd.
|
|
|
|
func createSocket(iface net.Interface, ifaceLink netlink.Link, enableGSO bool) (*socketEntry, error) {
|
|
|
|
// Create the socket.
|
|
|
|
const protocol = 0x0300 // htons(ETH_P_ALL)
|
|
|
|
fd, err := syscall.Socket(syscall.AF_PACKET, syscall.SOCK_RAW, protocol)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("unable to create raw socket: %v", err)
|
|
|
|
}
|
|
|
|
deviceFile := os.NewFile(uintptr(fd), "raw-device-fd")
|
|
|
|
// Bind to the appropriate device.
|
|
|
|
ll := syscall.SockaddrLinklayer{
|
|
|
|
Protocol: protocol,
|
|
|
|
Ifindex: iface.Index,
|
|
|
|
Hatype: 0, // No ARP type.
|
|
|
|
Pkttype: syscall.PACKET_OTHERHOST,
|
|
|
|
}
|
|
|
|
if err := syscall.Bind(fd, &ll); err != nil {
|
|
|
|
return nil, fmt.Errorf("unable to bind to %q: %v", iface.Name, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
gsoMaxSize := uint32(0)
|
|
|
|
if enableGSO {
|
|
|
|
gso, err := isGSOEnabled(fd, iface.Name)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("getting GSO for interface %q: %v", iface.Name, err)
|
|
|
|
}
|
|
|
|
if gso {
|
|
|
|
if err := syscall.SetsockoptInt(fd, syscall.SOL_PACKET, unix.PACKET_VNET_HDR, 1); err != nil {
|
|
|
|
return nil, fmt.Errorf("unable to enable the PACKET_VNET_HDR option: %v", err)
|
|
|
|
}
|
|
|
|
gsoMaxSize = ifaceLink.Attrs().GSOMaxSize
|
|
|
|
} else {
|
|
|
|
log.Infof("GSO not available in host.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Use SO_RCVBUFFORCE because on linux the receive buffer for an
|
|
|
|
// AF_PACKET socket is capped by "net.core.rmem_max". rmem_max
|
|
|
|
// defaults to a unusually low value of 208KB. This is too low
|
|
|
|
// for gVisor to be able to receive packets at high throughputs
|
|
|
|
// without incurring packet drops.
|
|
|
|
const rcvBufSize = 4 << 20 // 4MB.
|
|
|
|
|
|
|
|
if err := syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_RCVBUFFORCE, rcvBufSize); err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to increase socket rcv buffer to %d: %v", rcvBufSize, err)
|
|
|
|
}
|
|
|
|
return &socketEntry{deviceFile, gsoMaxSize}, nil
|
|
|
|
}
|
|
|
|
|
2018-04-27 17:37:02 +00:00
|
|
|
// loopbackLinks collects the links for a loopback interface.
|
|
|
|
func loopbackLinks(iface net.Interface, addrs []net.Addr) ([]boot.LoopbackLink, error) {
|
|
|
|
var links []boot.LoopbackLink
|
|
|
|
for _, addr := range addrs {
|
|
|
|
ipNet, ok := addr.(*net.IPNet)
|
|
|
|
if !ok {
|
2018-06-01 17:08:40 +00:00
|
|
|
return nil, fmt.Errorf("address is not IPNet: %+v", addr)
|
2018-04-27 17:37:02 +00:00
|
|
|
}
|
2019-08-21 22:30:13 +00:00
|
|
|
dst := *ipNet
|
|
|
|
dst.IP = dst.IP.Mask(dst.Mask)
|
2018-04-27 17:37:02 +00:00
|
|
|
links = append(links, boot.LoopbackLink{
|
|
|
|
Name: iface.Name,
|
|
|
|
Addresses: []net.IP{ipNet.IP},
|
|
|
|
Routes: []boot.Route{{
|
2019-08-21 22:30:13 +00:00
|
|
|
Destination: dst,
|
2018-04-27 17:37:02 +00:00
|
|
|
}},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return links, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// routesForIface iterates over all routes for the given interface and converts
|
|
|
|
// them to boot.Routes.
|
|
|
|
func routesForIface(iface net.Interface) ([]boot.Route, *boot.Route, error) {
|
|
|
|
link, err := netlink.LinkByIndex(iface.Index)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
rs, err := netlink.RouteList(link, netlink.FAMILY_ALL)
|
|
|
|
if err != nil {
|
2019-01-19 01:35:09 +00:00
|
|
|
return nil, nil, fmt.Errorf("getting routes from %q: %v", iface.Name, err)
|
2018-04-27 17:37:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var def *boot.Route
|
|
|
|
var routes []boot.Route
|
|
|
|
for _, r := range rs {
|
|
|
|
// Is it a default route?
|
|
|
|
if r.Dst == nil {
|
|
|
|
if r.Gw == nil {
|
|
|
|
return nil, nil, fmt.Errorf("default route with no gateway %q: %+v", iface.Name, r)
|
|
|
|
}
|
2018-06-01 17:08:40 +00:00
|
|
|
if r.Gw.To4() == nil {
|
|
|
|
log.Warningf("IPv6 is not supported, skipping default route: %v", r)
|
|
|
|
continue
|
|
|
|
}
|
2018-04-27 17:37:02 +00:00
|
|
|
if def != nil {
|
|
|
|
return nil, nil, fmt.Errorf("more than one default route found %q, def: %+v, route: %+v", iface.Name, def, r)
|
|
|
|
}
|
|
|
|
// Create a catch all route to the gateway.
|
|
|
|
def = &boot.Route{
|
2019-08-21 22:30:13 +00:00
|
|
|
Destination: net.IPNet{
|
|
|
|
IP: net.IPv4zero,
|
|
|
|
Mask: net.IPMask(net.IPv4zero),
|
|
|
|
},
|
|
|
|
Gateway: r.Gw,
|
2018-04-27 17:37:02 +00:00
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
2018-06-01 17:08:40 +00:00
|
|
|
if r.Dst.IP.To4() == nil {
|
|
|
|
log.Warningf("IPv6 is not supported, skipping route: %v", r)
|
|
|
|
continue
|
|
|
|
}
|
2019-08-21 22:30:13 +00:00
|
|
|
dst := *r.Dst
|
|
|
|
dst.IP = dst.IP.Mask(dst.Mask)
|
2018-04-27 17:37:02 +00:00
|
|
|
routes = append(routes, boot.Route{
|
2019-08-21 22:30:13 +00:00
|
|
|
Destination: dst,
|
2018-07-20 01:09:04 +00:00
|
|
|
Gateway: r.Gw,
|
2018-04-27 17:37:02 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
return routes, def, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// removeAddress removes IP address from network device. It's equivalent to:
|
|
|
|
// ip addr del <ipAndMask> dev <name>
|
|
|
|
func removeAddress(source netlink.Link, ipAndMask string) error {
|
|
|
|
addr, err := netlink.ParseAddr(ipAndMask)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return netlink.AddrDel(source, addr)
|
|
|
|
}
|