Merge pull request #534 from sboeuf/monitor_network_golang

virtcontainers: netmon: Monitor network changes
This commit is contained in:
Archana Shinde 2018-09-14 15:21:45 -07:00 committed by GitHub
commit 40bf14989d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 2012 additions and 172 deletions

1
.gitignore vendored
View File

@ -6,6 +6,7 @@
/cli/coverage.html
/cli/config-generated.go
/cli/config/configuration.toml
/kata-netmon
/virtcontainers/hack/virtc/virtc
/virtcontainers/hook/mock/hook
/virtcontainers/shim/mock/shim

4
Gopkg.lock generated
View File

@ -130,14 +130,14 @@
revision = "032705ba6aae05a9bf41e296cf89c8529cffb822"
[[projects]]
digest = "1:01c37fcb6e2a1fe1321a97faaef74c66ac531ea292ca3f929b7189cc400b1d47"
digest = "1:aec1ed6dbfffe247e5a8a9e3102206fe97552077e10ea44e3e35dce98ab6e5aa"
name = "github.com/kata-containers/agent"
packages = [
"protocols/client",
"protocols/grpc",
]
pruneopts = "NUT"
revision = "46396d205bf096db4e69fcfa319525858ce8050c"
revision = "7c95a50ef97052bf7f5566dcca53d6611f7458ac"
[[projects]]
digest = "1:04054595e5c5a35d1553a7f3464d18577caf597445d643992998643df56d4afd"

View File

@ -56,7 +56,7 @@
[[constraint]]
name = "github.com/kata-containers/agent"
revision = "46396d205bf096db4e69fcfa319525858ce8050c"
revision = "7c95a50ef97052bf7f5566dcca53d6611f7458ac"
[[constraint]]
name = "github.com/containerd/cri-containerd"

View File

@ -39,6 +39,7 @@ SCRIPTS :=
# list of binaries to install
BINLIST :=
BINLIBEXECLIST :=
BIN_PREFIX = $(PROJECT_TYPE)
PROJECT_DIR = $(PROJECT_TAG)
@ -49,6 +50,11 @@ TARGET = $(BIN_PREFIX)-runtime
TARGET_OUTPUT = $(CURDIR)/$(TARGET)
BINLIST += $(TARGET)
NETMON_DIR = netmon
NETMON_TARGET = $(PROJECT_TYPE)-netmon
NETMON_TARGET_OUTPUT = $(CURDIR)/$(NETMON_TARGET)
BINLIBEXECLIST += $(NETMON_TARGET)
DESTDIR := /
installing = $(findstring install,$(MAKECMDGOALS))
@ -110,6 +116,9 @@ SHIMPATH := $(PKGLIBEXECDIR)/$(SHIMCMD)
PROXYCMD := $(BIN_PREFIX)-proxy
PROXYPATH := $(PKGLIBEXECDIR)/$(PROXYCMD)
NETMONCMD := $(BIN_PREFIX)-netmon
NETMONPATH := $(PKGLIBEXECDIR)/$(NETMONCMD)
# Default number of vCPUs
DEFVCPUS := 1
# Default maximum number of vCPUs
@ -183,6 +192,7 @@ USER_VARS += PROJECT_NAME
USER_VARS += PROJECT_PREFIX
USER_VARS += PROJECT_TYPE
USER_VARS += PROXYPATH
USER_VARS += NETMONPATH
USER_VARS += QEMUBINDIR
USER_VARS += QEMUCMD
USER_VARS += QEMUPATH
@ -225,7 +235,12 @@ define SHOW_ARCH
$(shell printf "\\t%s%s\\\n" "$(1)" $(if $(filter $(ARCH),$(1))," (default)",""))
endef
all: runtime
all: runtime netmon
netmon: $(NETMON_TARGET_OUTPUT)
$(NETMON_TARGET_OUTPUT): $(SOURCES)
$(QUIET_BUILD)(cd $(NETMON_DIR) && go build -i -o $@ -ldflags "-X main.version=$(VERSION)")
runtime: $(TARGET_OUTPUT) $(CONFIG)
.DEFAULT: default
@ -308,6 +323,7 @@ var defaultRuntimeConfiguration = "$(CONFIG_PATH)"
var defaultSysConfRuntimeConfiguration = "$(SYSCONFIG)"
var defaultProxyPath = "$(PROXYPATH)"
var defaultNetmonPath = "$(NETMONPATH)"
endef
export GENERATED_CODE
@ -362,6 +378,7 @@ $(GENERATED_FILES): %: %.in Makefile VERSION
-e "s|@LOCALSTATEDIR@|$(LOCALSTATEDIR)|g" \
-e "s|@PKGLIBEXECDIR@|$(PKGLIBEXECDIR)|g" \
-e "s|@PROXYPATH@|$(PROXYPATH)|g" \
-e "s|@NETMONPATH@|$(NETMONPATH)|g" \
-e "s|@PROJECT_BUG_URL@|$(PROJECT_BUG_URL)|g" \
-e "s|@PROJECT_URL@|$(PROJECT_URL)|g" \
-e "s|@PROJECT_NAME@|$(PROJECT_NAME)|g" \
@ -405,11 +422,14 @@ check-go-static:
coverage:
$(QUIET_TEST).ci/go-test.sh html-coverage
install: default runtime install-scripts install-completions install-config install-bin
install: default runtime install-scripts install-completions install-config install-bin install-bin-libexec
install-bin: $(BINLIST)
$(foreach f,$(BINLIST),$(call INSTALL_EXEC,$f,$(BINDIR)))
install-bin-libexec: $(BINLIBEXECLIST)
$(foreach f,$(BINLIBEXECLIST),$(call INSTALL_EXEC,$f,$(PKGLIBEXECDIR)))
install-config: $(CONFIG)
$(QUIET_INST)install --mode 0644 -D $(CONFIG) $(DESTDIR)/$(CONFIG_PATH)
@ -420,7 +440,7 @@ install-completions:
$(QUIET_INST)install --mode 0644 -D $(BASH_COMPLETIONS) $(DESTDIR)/$(BASH_COMPLETIONSDIR)/$(notdir $(BASH_COMPLETIONS));
clean:
$(QUIET_CLEAN)rm -f $(TARGET) $(CONFIG) $(GENERATED_GO_FILES) $(GENERATED_FILES) $(COLLECT_SCRIPT)
$(QUIET_CLEAN)rm -f $(TARGET) $(NETMON_TARGET) $(CONFIG) $(GENERATED_GO_FILES) $(GENERATED_FILES) $(COLLECT_SCRIPT)
show-usage: show-header
@printf "• Overview:\n"
@ -483,6 +503,8 @@ show-summary: show-header
@printf "\tbinaries to install :\n"
@printf \
"$(foreach b,$(sort $(BINLIST)),$(shell printf "\\t - $(shell readlink -m $(DESTDIR)/$(BINDIR)/$(b))\\\n"))"
@printf \
"$(foreach b,$(sort $(BINLIBEXECLIST)),$(shell printf "\\t - $(shell readlink -m $(DESTDIR)/$(PKGLIBEXECDIR)/$(b))\\\n"))"
@printf \
"$(foreach s,$(sort $(SCRIPTS)),$(shell printf "\\t - $(shell readlink -m $(DESTDIR)/$(BINDIR)/$(s))\\\n"))"
@printf "\tconfig to install (CONFIG) : %s\n" $(CONFIG)

View File

@ -64,6 +64,7 @@ type tomlConfig struct {
Agent map[string]agent
Runtime runtime
Factory factory
Netmon netmon
}
type factory struct {
@ -116,6 +117,12 @@ type shim struct {
type agent struct {
}
type netmon struct {
Path string `toml:"path"`
Debug bool `toml:"enable_debug"`
Enable bool `toml:"enable_netmon"`
}
func (h hypervisor) path() (string, error) {
p := h.Path
@ -302,6 +309,22 @@ func (s shim) debug() bool {
return s.Debug
}
func (n netmon) enable() bool {
return n.Enable
}
func (n netmon) path() string {
if n.Path == "" {
return defaultNetmonPath
}
return n.Path
}
func (n netmon) debug() bool {
return n.Debug
}
func newQemuHypervisorConfig(h hypervisor) (vc.HypervisorConfig, error) {
hypervisor, err := h.path()
if err != nil {
@ -464,6 +487,12 @@ func updateRuntimeConfig(configPath string, tomlConf tomlConfig, config *oci.Run
}
config.FactoryConfig = fConfig
config.NetmonConfig = vc.NetmonConfig{
Path: tomlConf.Netmon.path(),
Debug: tomlConf.Netmon.debug(),
Enable: tomlConf.Netmon.enable(),
}
return nil
}

View File

@ -181,6 +181,21 @@ path = "@SHIMPATH@"
# There is no field for this section. The goal is only to be able to
# specify which type of agent the user wants to use.
[netmon]
# If enabled, the network monitoring process gets started when the
# sandbox is created. This allows for the detection of some additional
# network being added to the existing network namespace, after the
# sandbox has been created.
# (default: disabled)
#enable_netmon = true
# Specify the path to the netmon binary.
path = "@NETMONPATH@"
# If enabled, netmon messages will be sent to the system log
# (default: disabled)
#enable_debug = true
[runtime]
# If enabled, the runtime will log additional debug messages to the
# system log

View File

@ -30,6 +30,7 @@ var (
proxyDebug = false
runtimeDebug = false
shimDebug = false
netmonDebug = false
)
type testRuntimeConfig struct {
@ -41,7 +42,7 @@ type testRuntimeConfig struct {
LogPath string
}
func makeRuntimeConfigFileData(hypervisor, hypervisorPath, kernelPath, imagePath, kernelParams, machineType, shimPath, proxyPath, logPath string, disableBlock bool, blockDeviceDriver string, enableIOThreads bool, hotplugVFIOOnRootBus bool) string {
func makeRuntimeConfigFileData(hypervisor, hypervisorPath, kernelPath, imagePath, kernelParams, machineType, shimPath, proxyPath, netmonPath, logPath string, disableBlock bool, blockDeviceDriver string, enableIOThreads bool, hotplugVFIOOnRootBus bool) string {
return `
# Runtime configuration file
@ -71,6 +72,10 @@ func makeRuntimeConfigFileData(hypervisor, hypervisorPath, kernelPath, imagePath
[agent.kata]
[netmon]
path = "` + netmonPath + `"
enable_debug = ` + strconv.FormatBool(netmonDebug) + `
[runtime]
enable_debug = ` + strconv.FormatBool(runtimeDebug)
}
@ -103,6 +108,7 @@ func createAllRuntimeConfigFiles(dir, hypervisor string) (config testRuntimeConf
imagePath := path.Join(dir, "image")
shimPath := path.Join(dir, "shim")
proxyPath := path.Join(dir, "proxy")
netmonPath := path.Join(dir, "netmon")
logDir := path.Join(dir, "logs")
logPath := path.Join(logDir, "runtime.log")
machineType := "machineType"
@ -111,7 +117,7 @@ func createAllRuntimeConfigFiles(dir, hypervisor string) (config testRuntimeConf
enableIOThreads := true
hotplugVFIOOnRootBus := true
runtimeConfigFileData := makeRuntimeConfigFileData(hypervisor, hypervisorPath, kernelPath, imagePath, kernelParams, machineType, shimPath, proxyPath, logPath, disableBlockDevice, blockDeviceDriver, enableIOThreads, hotplugVFIOOnRootBus)
runtimeConfigFileData := makeRuntimeConfigFileData(hypervisor, hypervisorPath, kernelPath, imagePath, kernelParams, machineType, shimPath, proxyPath, netmonPath, logPath, disableBlockDevice, blockDeviceDriver, enableIOThreads, hotplugVFIOOnRootBus)
configPath := path.Join(dir, "runtime.toml")
err = createConfig(configPath, runtimeConfigFileData)
@ -165,6 +171,12 @@ func createAllRuntimeConfigFiles(dir, hypervisor string) (config testRuntimeConf
Path: shimPath,
}
netmonConfig := vc.NetmonConfig{
Path: netmonPath,
Debug: false,
Enable: false,
}
runtimeConfig := oci.RuntimeConfig{
HypervisorType: defaultHypervisor,
HypervisorConfig: hypervisorConfig,
@ -177,6 +189,8 @@ func createAllRuntimeConfigFiles(dir, hypervisor string) (config testRuntimeConf
ShimType: defaultShim,
ShimConfig: shimConfig,
NetmonConfig: netmonConfig,
}
config = testRuntimeConfig{
@ -482,11 +496,11 @@ func TestMinimalRuntimeConfig(t *testing.T) {
proxyPath := path.Join(dir, "proxy")
hypervisorPath := path.Join(dir, "hypervisor")
defaultHypervisorPath = hypervisorPath
netmonPath := path.Join(dir, "netmon")
imagePath := path.Join(dir, "image.img")
initrdPath := path.Join(dir, "initrd.img")
hypervisorPath = path.Join(dir, "hypervisor")
kernelPath := path.Join(dir, "kernel")
savedDefaultImagePath := defaultImagePath
@ -525,6 +539,9 @@ func TestMinimalRuntimeConfig(t *testing.T) {
path = "` + shimPath + `"
[agent.kata]
[netmon]
path = "` + netmonPath + `"
`
configPath := path.Join(dir, "runtime.toml")
@ -553,6 +570,11 @@ func TestMinimalRuntimeConfig(t *testing.T) {
t.Error(err)
}
err = createEmptyFile(netmonPath)
if err != nil {
t.Error(err)
}
_, config, err = loadConfiguration(configPath, false)
if err != nil {
t.Fatal(err)
@ -584,6 +606,12 @@ func TestMinimalRuntimeConfig(t *testing.T) {
Path: shimPath,
}
expectedNetmonConfig := vc.NetmonConfig{
Path: netmonPath,
Debug: false,
Enable: false,
}
expectedConfig := oci.RuntimeConfig{
HypervisorType: defaultHypervisor,
HypervisorConfig: expectedHypervisorConfig,
@ -596,6 +624,8 @@ func TestMinimalRuntimeConfig(t *testing.T) {
ShimType: defaultShim,
ShimConfig: expectedShimConfig,
NetmonConfig: expectedNetmonConfig,
}
if reflect.DeepEqual(config, expectedConfig) == false {

View File

@ -25,7 +25,7 @@ import (
//
// XXX: Increment for every change to the output format
// (meaning any change to the EnvInfo type).
const formatVersion = "1.0.15"
const formatVersion = "1.0.16"
// MetaInfo stores information on the format of the output itself
type MetaInfo struct {
@ -123,6 +123,14 @@ type HostInfo struct {
SupportVSocks bool
}
// NetmonInfo stores netmon details
type NetmonInfo struct {
Version string
Path string
Debug bool
Enable bool
}
// EnvInfo collects all information that will be displayed by the
// env command.
//
@ -138,6 +146,7 @@ type EnvInfo struct {
Shim ShimInfo
Agent AgentInfo
Host HostInfo
Netmon NetmonInfo
}
func getMetaInfo() MetaInfo {
@ -241,6 +250,22 @@ func getProxyInfo(config oci.RuntimeConfig) (ProxyInfo, error) {
return proxy, nil
}
func getNetmonInfo(config oci.RuntimeConfig) (NetmonInfo, error) {
version, err := getCommandVersion(defaultNetmonPath)
if err != nil {
version = unknown
}
netmon := NetmonInfo{
Version: version,
Path: config.NetmonConfig.Path,
Debug: config.NetmonConfig.Debug,
Enable: config.NetmonConfig.Enable,
}
return netmon, nil
}
func getCommandVersion(cmd string) (string, error) {
return runCommand([]string{cmd, "--version"})
}
@ -309,6 +334,8 @@ func getEnvInfo(configFile string, config oci.RuntimeConfig) (env EnvInfo, err e
proxy, _ := getProxyInfo(config)
netmon, _ := getNetmonInfo(config)
shim, err := getShimInfo(config)
if err != nil {
return EnvInfo{}, err
@ -342,6 +369,7 @@ func getEnvInfo(configFile string, config oci.RuntimeConfig) (env EnvInfo, err e
Shim: shim,
Agent: agent,
Host: host,
Netmon: netmon,
}
return env, nil

View File

@ -31,6 +31,7 @@ import (
const testProxyURL = "file:///proxyURL"
const testProxyVersion = "proxy version 0.1"
const testShimVersion = "shim version 0.1"
const testNetmonVersion = "netmon version 0.1"
const testHypervisorVersion = "QEMU emulator version 2.7.0+git.741f430a96-6.1, Copyright (c) 2003-2016 Fabrice Bellard and the QEMU Project developers"
// makeVersionBinary creates a shell script with the specified file
@ -61,6 +62,7 @@ func makeRuntimeConfig(prefixDir string) (configFile string, config oci.RuntimeC
machineType := "machineType"
shimPath := filepath.Join(prefixDir, "shim")
proxyPath := filepath.Join(prefixDir, "proxy")
netmonPath := filepath.Join(prefixDir, "netmon")
disableBlock := true
blockStorageDriver := "virtio-scsi"
enableIOThreads := true
@ -68,6 +70,7 @@ func makeRuntimeConfig(prefixDir string) (configFile string, config oci.RuntimeC
// override
defaultProxyPath = proxyPath
defaultNetmonPath = netmonPath
filesToCreate := []string{
hypervisorPath,
@ -93,6 +96,11 @@ func makeRuntimeConfig(prefixDir string) (configFile string, config oci.RuntimeC
return "", oci.RuntimeConfig{}, err
}
err = makeVersionBinary(netmonPath, testNetmonVersion)
if err != nil {
return "", oci.RuntimeConfig{}, err
}
err = makeVersionBinary(hypervisorPath, testHypervisorVersion)
if err != nil {
return "", oci.RuntimeConfig{}, err
@ -107,6 +115,7 @@ func makeRuntimeConfig(prefixDir string) (configFile string, config oci.RuntimeC
machineType,
shimPath,
testProxyURL,
netmonPath,
logPath,
disableBlock,
blockStorageDriver,
@ -137,6 +146,15 @@ func getExpectedProxyDetails(config oci.RuntimeConfig) (ProxyInfo, error) {
}, nil
}
func getExpectedNetmonDetails(config oci.RuntimeConfig) (NetmonInfo, error) {
return NetmonInfo{
Version: testNetmonVersion,
Path: config.NetmonConfig.Path,
Debug: config.NetmonConfig.Debug,
Enable: config.NetmonConfig.Enable,
}, nil
}
func getExpectedShimDetails(config oci.RuntimeConfig) (ShimInfo, error) {
shimConfig, ok := config.ShimConfig.(vc.ShimConfig)
if !ok {
@ -303,6 +321,11 @@ func getExpectedSettings(config oci.RuntimeConfig, tmpdir, configFile string) (E
return EnvInfo{}, err
}
netmon, err := getExpectedNetmonDetails(config)
if err != nil {
return EnvInfo{}, err
}
hypervisor := getExpectedHypervisor(config)
kernel := getExpectedKernel(config)
image := getExpectedImage(config)
@ -317,6 +340,7 @@ func getExpectedSettings(config oci.RuntimeConfig, tmpdir, configFile string) (E
Shim: shim,
Agent: agent,
Host: host,
Netmon: netmon,
}
return env, nil
@ -608,6 +632,50 @@ func TestEnvGetProxyInfoNoVersion(t *testing.T) {
assert.Equal(t, expectedProxy, proxy)
}
func TestEnvGetNetmonInfo(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "")
if err != nil {
panic(err)
}
defer os.RemoveAll(tmpdir)
_, config, err := makeRuntimeConfig(tmpdir)
assert.NoError(t, err)
expectedNetmon, err := getExpectedNetmonDetails(config)
assert.NoError(t, err)
netmon, err := getNetmonInfo(config)
assert.NoError(t, err)
assert.Equal(t, expectedNetmon, netmon)
}
func TestEnvGetNetmonInfoNoVersion(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "")
if err != nil {
panic(err)
}
defer os.RemoveAll(tmpdir)
_, config, err := makeRuntimeConfig(tmpdir)
assert.NoError(t, err)
expectedNetmon, err := getExpectedNetmonDetails(config)
assert.NoError(t, err)
// remove the netmon ensuring its version cannot be queried
err = os.Remove(defaultNetmonPath)
assert.NoError(t, err)
expectedNetmon.Version = unknown
netmon, err := getNetmonInfo(config)
assert.NoError(t, err)
assert.Equal(t, expectedNetmon, netmon)
}
func TestEnvGetShimInfo(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "")
if err != nil {

661
netmon/netmon.go Normal file
View File

@ -0,0 +1,661 @@
// Copyright (c) 2018 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"log/syslog"
"net"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/sirupsen/logrus"
lSyslog "github.com/sirupsen/logrus/hooks/syslog"
"github.com/vishvananda/netlink"
"golang.org/x/sys/unix"
)
// The following types and structures have to be kept in sync with the
// description of the agent protocol. Those definitions need to be in their
// own separate package so that they can be imported directly from this code.
// The reason for not importing them now, is because importing the whole agent
// protocol adds up too much overhead because of the grpc protocol involved.
// IPFamily define the IP address family type.
type IPFamily int32
// IPAddress describes the IP address format expected by Kata API.
type IPAddress struct {
Family IPFamily `json:"family,omitempty"`
Address string `json:"address,omitempty"`
Mask string `json:"mask,omitempty"`
}
// Interface describes the network interface format expected by Kata API.
type Interface struct {
Device string `json:"device,omitempty"`
Name string `json:"name,omitempty"`
IPAddresses []*IPAddress `json:"IPAddresses,omitempty"`
Mtu uint64 `json:"mtu,omitempty"`
HwAddr string `json:"hwAddr,omitempty"`
PciAddr string `json:"pciAddr,omitempty"`
}
// Route describes the network route format expected by Kata API.
type Route struct {
Dest string `json:"dest,omitempty"`
Gateway string `json:"gateway,omitempty"`
Device string `json:"device,omitempty"`
Source string `json:"source,omitempty"`
Scope uint32 `json:"scope,omitempty"`
}
const (
netmonName = "kata-netmon"
kataCmd = "kata-network"
kataCLIAddIfaceCmd = "add-iface"
kataCLIDelIfaceCmd = "del-iface"
kataCLIUpdtRoutesCmd = "update-routes"
kataSuffix = "kata"
// sharedFile is the name of the file that will be used to share
// the data between this process and the kata-runtime process
// responsible for updating the network.
sharedFile = "shared.json"
storageFilePerm = os.FileMode(0640)
storageDirPerm = os.FileMode(0750)
)
var (
// version is the netmon version. This variable is populated at build time.
version = "unknown"
// For simplicity the code will only focus on IPv4 addresses for now.
netlinkFamily = netlink.FAMILY_V4
storageParentPath = "/var/run/kata-containers/netmon/sbs"
)
type netmonParams struct {
sandboxID string
runtimePath string
debug bool
logLevel string
}
type netmon struct {
netmonParams
storagePath string
sharedFile string
netIfaces map[int]Interface
linkUpdateCh chan netlink.LinkUpdate
linkDoneCh chan struct{}
rtUpdateCh chan netlink.RouteUpdate
rtDoneCh chan struct{}
netHandler *netlink.Handle
}
var netmonLog = logrus.New()
func printVersion() {
fmt.Printf("%s version %s\n", netmonName, version)
}
const componentDescription = `is a network monitoring process that is intended to be started in the
appropriate network namespace so that it can listen to any event related to
link and routes. Whenever a new interface or route is created/updated, it is
responsible for calling into the kata-runtime CLI to ask for the actual
creation/update of the given interface or route.
`
func printComponentDescription() {
fmt.Printf("\n%s %s\n", netmonName, componentDescription)
}
func parseOptions() netmonParams {
var version, help bool
params := netmonParams{}
flag.BoolVar(&help, "h", false, "describe component usage")
flag.BoolVar(&help, "help", false, "")
flag.BoolVar(&params.debug, "d", false, "enable debug mode")
flag.BoolVar(&version, "v", false, "display program version and exit")
flag.BoolVar(&version, "version", false, "")
flag.StringVar(&params.sandboxID, "s", "", "sandbox id (required)")
flag.StringVar(&params.runtimePath, "r", "", "runtime path (required)")
flag.StringVar(&params.logLevel, "log", "warn",
"log messages above specified level: debug, warn, error, fatal or panic")
flag.Parse()
if help {
printComponentDescription()
flag.PrintDefaults()
os.Exit(0)
}
if version {
printVersion()
os.Exit(0)
}
if params.sandboxID == "" {
fmt.Fprintf(os.Stderr, "Error: sandbox id is empty, one must be provided\n")
flag.PrintDefaults()
os.Exit(1)
}
if params.runtimePath == "" {
fmt.Fprintf(os.Stderr, "Error: runtime path is empty, one must be provided\n")
flag.PrintDefaults()
os.Exit(1)
}
return params
}
func newNetmon(params netmonParams) (*netmon, error) {
handler, err := netlink.NewHandle(netlinkFamily)
if err != nil {
return nil, err
}
n := &netmon{
netmonParams: params,
storagePath: filepath.Join(storageParentPath, params.sandboxID),
sharedFile: filepath.Join(storageParentPath, params.sandboxID, sharedFile),
netIfaces: make(map[int]Interface),
linkUpdateCh: make(chan netlink.LinkUpdate),
linkDoneCh: make(chan struct{}),
rtUpdateCh: make(chan netlink.RouteUpdate),
rtDoneCh: make(chan struct{}),
netHandler: handler,
}
if err := os.MkdirAll(n.storagePath, storageDirPerm); err != nil {
return nil, err
}
return n, nil
}
func (n *netmon) cleanup() {
os.RemoveAll(n.storagePath)
n.netHandler.Delete()
close(n.linkDoneCh)
close(n.rtDoneCh)
}
func (n *netmon) logger() *logrus.Entry {
fields := logrus.Fields{
"name": netmonName,
"pid": os.Getpid(),
"source": "netmon",
}
if n.sandboxID != "" {
fields["sandbox"] = n.sandboxID
}
return netmonLog.WithFields(fields)
}
func (n *netmon) setupLogger() error {
level, err := logrus.ParseLevel(n.logLevel)
if err != nil {
return err
}
netmonLog.SetLevel(level)
netmonLog.Formatter = &logrus.TextFormatter{TimestampFormat: time.RFC3339Nano}
hook, err := lSyslog.NewSyslogHook("", "", syslog.LOG_INFO|syslog.LOG_USER, netmonName)
if err != nil {
return err
}
netmonLog.AddHook(hook)
announceFields := logrus.Fields{
"runtime-path": n.runtimePath,
"debug": n.debug,
"log-level": n.logLevel,
}
n.logger().WithFields(announceFields).Info("announce")
return nil
}
func (n *netmon) listenNetlinkEvents() error {
if err := netlink.LinkSubscribe(n.linkUpdateCh, n.linkDoneCh); err != nil {
return err
}
return netlink.RouteSubscribe(n.rtUpdateCh, n.rtDoneCh)
}
// convertInterface converts a link and its IP addresses as defined by netlink
// package, into the Interface structure format expected by kata-runtime to
// describe an interface and its associated IP addresses.
func convertInterface(linkAttrs *netlink.LinkAttrs, addrs []netlink.Addr) Interface {
if linkAttrs == nil {
netmonLog.Warn("Link attributes are nil")
return Interface{}
}
var ipAddrs []*IPAddress
for _, addr := range addrs {
if addr.IPNet == nil {
continue
}
netMask, _ := addr.Mask.Size()
ipAddr := &IPAddress{
Family: IPFamily(netlinkFamily),
Address: addr.IP.String(),
Mask: fmt.Sprintf("%d", netMask),
}
ipAddrs = append(ipAddrs, ipAddr)
}
iface := Interface{
Device: linkAttrs.Name,
Name: linkAttrs.Name,
IPAddresses: ipAddrs,
Mtu: uint64(linkAttrs.MTU),
HwAddr: linkAttrs.HardwareAddr.String(),
}
netmonLog.WithField("interface", iface).Debug("Interface converted")
return iface
}
// convertRoutes converts a list of routes as defined by netlink package,
// into a list of Route structure format expected by kata-runtime to
// describe a set of routes.
func convertRoutes(netRoutes []netlink.Route) []Route {
var routes []Route
// Ignore routes with IPv6 addresses as this is not supported
// by Kata yet.
for _, netRoute := range netRoutes {
dst := ""
if netRoute.Dst != nil {
if netRoute.Dst.IP.To4() != nil {
dst = netRoute.Dst.IP.String()
} else {
netmonLog.WithField("destination", netRoute.Dst.IP.String()).Warn("Not IPv4 format")
}
}
src := ""
if netRoute.Src.To4() != nil {
src = netRoute.Src.String()
} else {
netmonLog.WithField("source", netRoute.Src.String()).Warn("Not IPv4 format")
}
gw := ""
if netRoute.Gw.To4() != nil {
gw = netRoute.Gw.String()
} else {
netmonLog.WithField("gateway", netRoute.Gw.String()).Warn("Not IPv4 format")
}
dev := ""
iface, err := net.InterfaceByIndex(netRoute.LinkIndex)
if err == nil {
dev = iface.Name
}
route := Route{
Dest: dst,
Gateway: gw,
Device: dev,
Source: src,
Scope: uint32(netRoute.Scope),
}
routes = append(routes, route)
}
netmonLog.WithField("routes", routes).Debug("Routes converted")
return routes
}
// scanNetwork lists all the interfaces it can find inside the current
// network namespace, and store them in-memory to keep track of them.
func (n *netmon) scanNetwork() error {
links, err := n.netHandler.LinkList()
if err != nil {
return err
}
for _, link := range links {
addrs, err := n.netHandler.AddrList(link, netlinkFamily)
if err != nil {
return err
}
linkAttrs := link.Attrs()
if linkAttrs == nil {
continue
}
iface := convertInterface(linkAttrs, addrs)
n.netIfaces[linkAttrs.Index] = iface
}
n.logger().Debug("Network scanned")
return nil
}
func (n *netmon) storeDataToSend(data interface{}) error {
// Marshal the data structure into a JSON bytes array.
jsonArray, err := json.Marshal(data)
if err != nil {
return err
}
// Store the JSON bytes array at the specified path.
return ioutil.WriteFile(n.sharedFile, jsonArray, storageFilePerm)
}
func (n *netmon) execKataCmd(subCmd string) error {
execCmd := exec.Command(n.runtimePath, kataCmd, subCmd, n.sandboxID, n.sharedFile)
n.logger().WithField("command", execCmd).Debug("Running runtime command")
// Make use of Run() to ensure the kata-runtime process has correctly
// terminated before to go further.
if err := execCmd.Run(); err != nil {
return err
}
// Remove the shared file after the command returned. At this point
// we know the content of the file is not going to be used anymore,
// and the file path can be reused for further commands.
return os.Remove(n.sharedFile)
}
func (n *netmon) addInterfaceCLI(iface Interface) error {
if err := n.storeDataToSend(iface); err != nil {
return err
}
return n.execKataCmd(kataCLIAddIfaceCmd)
}
func (n *netmon) delInterfaceCLI(iface Interface) error {
if err := n.storeDataToSend(iface); err != nil {
return err
}
return n.execKataCmd(kataCLIDelIfaceCmd)
}
func (n *netmon) updateRoutesCLI(routes []Route) error {
if err := n.storeDataToSend(routes); err != nil {
return err
}
return n.execKataCmd(kataCLIUpdtRoutesCmd)
}
func (n *netmon) updateRoutes() error {
// Get all the routes.
netlinkRoutes, err := n.netHandler.RouteList(nil, netlinkFamily)
if err != nil {
return err
}
// Translate them into Route structures.
routes := convertRoutes(netlinkRoutes)
// Update the routes through the Kata CLI.
return n.updateRoutesCLI(routes)
}
func (n *netmon) handleRTMNewAddr(ev netlink.LinkUpdate) error {
n.logger().Debug("Interface update not supported")
return nil
}
func (n *netmon) handleRTMDelAddr(ev netlink.LinkUpdate) error {
n.logger().Debug("Interface update not supported")
return nil
}
func (n *netmon) handleRTMNewLink(ev netlink.LinkUpdate) error {
// NEWLINK might be a lot of different things. We're interested in
// adding the interface (both to our list and by calling into the
// Kata CLI API) only if this has the flags UP and RUNNING, meaning
// we don't expect any further change on the interface, and that we
// are ready to add it.
linkAttrs := ev.Link.Attrs()
if linkAttrs == nil {
n.logger().Warn("The link attributes are nil")
return nil
}
// First, ignore if the interface name contains "kata". This way we
// are preventing from adding interfaces created by Kata Containers.
if strings.HasSuffix(linkAttrs.Name, kataSuffix) {
n.logger().Debugf("Ignore the interface %s because found %q",
linkAttrs.Name, kataSuffix)
return nil
}
// Check if the interface exist in the internal list.
if _, exist := n.netIfaces[int(ev.Index)]; exist {
n.logger().Debugf("Ignoring interface %s because already exist",
linkAttrs.Name)
return nil
}
// Now, check if the interface has been enabled to UP and RUNNING.
if (ev.Flags&unix.IFF_UP) != unix.IFF_UP ||
(ev.Flags&unix.IFF_RUNNING) != unix.IFF_RUNNING {
n.logger().Debugf("Ignore the interface %s because not UP and RUNNING",
linkAttrs.Name)
return nil
}
// Get the list of IP addresses associated with this interface.
addrs, err := n.netHandler.AddrList(ev.Link, netlinkFamily)
if err != nil {
return err
}
// Convert the interfaces in the appropriate structure format.
iface := convertInterface(linkAttrs, addrs)
// Add the interface through the Kata CLI.
if err := n.addInterfaceCLI(iface); err != nil {
return err
}
// Add the interface to the internal list.
n.netIfaces[linkAttrs.Index] = iface
// Complete by updating the routes.
return n.updateRoutes()
}
func (n *netmon) handleRTMDelLink(ev netlink.LinkUpdate) error {
// It can only delete if identical interface is found in the internal
// list of interfaces. Otherwise, the deletion will be ignored.
linkAttrs := ev.Link.Attrs()
if linkAttrs == nil {
n.logger().Warn("Link attributes are nil")
return nil
}
// First, ignore if the interface name contains "kata". This way we
// are preventing from deleting interfaces created by Kata Containers.
if strings.Contains(linkAttrs.Name, kataSuffix) {
n.logger().Debugf("Ignore the interface %s because found %q",
linkAttrs.Name, kataSuffix)
return nil
}
// Check if the interface exist in the internal list.
iface, exist := n.netIfaces[int(ev.Index)]
if !exist {
n.logger().Debugf("Ignoring interface %s because not found",
linkAttrs.Name)
return nil
}
if err := n.delInterfaceCLI(iface); err != nil {
return err
}
// Delete the interface from the internal list.
delete(n.netIfaces, linkAttrs.Index)
// Complete by updating the routes.
return n.updateRoutes()
}
func (n *netmon) handleRTMNewRoute(ev netlink.RouteUpdate) error {
// Add the route through updateRoutes(), only if the route refer to an
// interface that already exists in the internal list of interfaces.
if _, exist := n.netIfaces[ev.Route.LinkIndex]; !exist {
n.logger().Debugf("Ignoring route %+v since interface %d not found",
ev.Route, ev.Route.LinkIndex)
return nil
}
return n.updateRoutes()
}
func (n *netmon) handleRTMDelRoute(ev netlink.RouteUpdate) error {
// Remove the route through updateRoutes(), only if the route refer to
// an interface that already exists in the internal list of interfaces.
return n.updateRoutes()
}
func (n *netmon) handleLinkEvent(ev netlink.LinkUpdate) error {
n.logger().Debug("handleLinkEvent: netlink event received")
switch ev.Header.Type {
case unix.NLMSG_DONE:
n.logger().Debug("NLMSG_DONE")
return nil
case unix.NLMSG_ERROR:
n.logger().Error("NLMSG_ERROR")
return fmt.Errorf("Error while listening on netlink socket")
case unix.RTM_NEWADDR:
n.logger().Debug("RTM_NEWADDR")
return n.handleRTMNewAddr(ev)
case unix.RTM_DELADDR:
n.logger().Debug("RTM_DELADDR")
return n.handleRTMDelAddr(ev)
case unix.RTM_NEWLINK:
n.logger().Debug("RTM_NEWLINK")
return n.handleRTMNewLink(ev)
case unix.RTM_DELLINK:
n.logger().Debug("RTM_DELLINK")
return n.handleRTMDelLink(ev)
default:
n.logger().Warnf("Unknown msg type %v", ev.Header.Type)
}
return nil
}
func (n *netmon) handleRouteEvent(ev netlink.RouteUpdate) error {
n.logger().Debug("handleRouteEvent: netlink event received")
switch ev.Type {
case unix.RTM_NEWROUTE:
n.logger().Debug("RTM_NEWROUTE")
return n.handleRTMNewRoute(ev)
case unix.RTM_DELROUTE:
n.logger().Debug("RTM_DELROUTE")
return n.handleRTMDelRoute(ev)
default:
n.logger().Warnf("Unknown msg type %v", ev.Type)
}
return nil
}
func (n *netmon) handleEvents() (err error) {
for {
select {
case ev := <-n.linkUpdateCh:
if err = n.handleLinkEvent(ev); err != nil {
return err
}
case ev := <-n.rtUpdateCh:
if err = n.handleRouteEvent(ev); err != nil {
return err
}
}
}
}
func main() {
// Parse parameters.
params := parseOptions()
// Create netmon handler.
n, err := newNetmon(params)
if err != nil {
netmonLog.WithError(err).Fatal("newNetmon()")
os.Exit(1)
}
defer n.cleanup()
// Init logger.
if err := n.setupLogger(); err != nil {
netmonLog.WithError(err).Fatal("setupLogger()")
os.Exit(1)
}
// Scan the current interfaces.
if err := n.scanNetwork(); err != nil {
n.logger().WithError(err).Fatal("scanNetwork()")
os.Exit(1)
}
// Subscribe to the link listener.
if err := n.listenNetlinkEvents(); err != nil {
n.logger().WithError(err).Fatal("listenNetlinkEvents()")
os.Exit(1)
}
// Go into the main loop.
if err := n.handleEvents(); err != nil {
n.logger().WithError(err).Fatal("handleEvents()")
os.Exit(1)
}
}

632
netmon/netmon_test.go Normal file
View File

@ -0,0 +1,632 @@
// Copyright (c) 2018 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"encoding/json"
"io/ioutil"
"net"
"os"
"os/exec"
"path/filepath"
"reflect"
"runtime"
"testing"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/vishvananda/netlink"
"github.com/vishvananda/netns"
"golang.org/x/sys/unix"
)
const (
testSandboxID = "123456789"
testRuntimePath = "/foo/bar/test-runtime"
testLogLevel = "info"
testStorageParentPath = "/tmp/netmon"
testSharedFile = "foo-shared.json"
testWrongNetlinkFamily = -1
testIfaceName = "test_eth0"
testMTU = 12345
testHwAddr = "02:00:ca:fe:00:48"
testIPAddress = "192.168.0.15"
testIPAddressWithMask = "192.168.0.15/32"
testScope = 1
testTxQLen = -1
testIfaceIndex = 5
)
func skipUnlessRoot(t *testing.T) {
if os.Getuid() != 0 {
t.Skip("Test disabled as requires root user")
}
}
func TestNewNetmon(t *testing.T) {
skipUnlessRoot(t)
// Override storageParentPath
savedStorageParentPath := storageParentPath
storageParentPath = testStorageParentPath
defer func() {
storageParentPath = savedStorageParentPath
}()
params := netmonParams{
sandboxID: testSandboxID,
runtimePath: testRuntimePath,
debug: true,
logLevel: testLogLevel,
}
expected := &netmon{
netmonParams: params,
storagePath: filepath.Join(storageParentPath, params.sandboxID),
sharedFile: filepath.Join(storageParentPath, params.sandboxID, sharedFile),
}
os.RemoveAll(expected.storagePath)
got, err := newNetmon(params)
assert.Nil(t, err)
assert.True(t, reflect.DeepEqual(expected.netmonParams, got.netmonParams),
"Got %+v\nExpected %+v", got.netmonParams, expected.netmonParams)
assert.True(t, reflect.DeepEqual(expected.storagePath, got.storagePath),
"Got %+v\nExpected %+v", got.storagePath, expected.storagePath)
assert.True(t, reflect.DeepEqual(expected.sharedFile, got.sharedFile),
"Got %+v\nExpected %+v", got.sharedFile, expected.sharedFile)
_, err = os.Stat(got.storagePath)
assert.Nil(t, err)
os.RemoveAll(got.storagePath)
}
func TestNewNetmonErrorWrongFamilyType(t *testing.T) {
// Override netlinkFamily
savedNetlinkFamily := netlinkFamily
netlinkFamily = testWrongNetlinkFamily
defer func() {
netlinkFamily = savedNetlinkFamily
}()
n, err := newNetmon(netmonParams{})
assert.NotNil(t, err)
assert.Nil(t, n)
}
func TestCleanup(t *testing.T) {
skipUnlessRoot(t)
// Override storageParentPath
savedStorageParentPath := storageParentPath
storageParentPath = testStorageParentPath
defer func() {
storageParentPath = savedStorageParentPath
}()
handler, err := netlink.NewHandle(netlinkFamily)
assert.Nil(t, err)
n := &netmon{
storagePath: filepath.Join(storageParentPath, testSandboxID),
linkDoneCh: make(chan struct{}),
rtDoneCh: make(chan struct{}),
netHandler: handler,
}
err = os.MkdirAll(n.storagePath, storageDirPerm)
assert.Nil(t, err)
_, err = os.Stat(n.storagePath)
assert.Nil(t, err)
n.cleanup()
_, err = os.Stat(n.storagePath)
assert.NotNil(t, err)
_, ok := (<-n.linkDoneCh)
assert.False(t, ok)
_, ok = (<-n.rtDoneCh)
assert.False(t, ok)
}
func TestLogger(t *testing.T) {
fields := logrus.Fields{
"name": netmonName,
"pid": os.Getpid(),
"source": "netmon",
"sandbox": testSandboxID,
}
expected := netmonLog.WithFields(fields)
n := &netmon{
netmonParams: netmonParams{
sandboxID: testSandboxID,
},
}
got := n.logger()
assert.True(t, reflect.DeepEqual(*expected, *got),
"Got %+v\nExpected %+v", *got, *expected)
}
func TestConvertInterface(t *testing.T) {
hwAddr, err := net.ParseMAC(testHwAddr)
assert.Nil(t, err)
addrs := []netlink.Addr{
{
IPNet: &net.IPNet{
IP: net.ParseIP(testIPAddress),
},
},
}
linkAttrs := &netlink.LinkAttrs{
Name: testIfaceName,
MTU: testMTU,
HardwareAddr: hwAddr,
}
expected := Interface{
Device: testIfaceName,
Name: testIfaceName,
Mtu: uint64(testMTU),
HwAddr: testHwAddr,
IPAddresses: []*IPAddress{
{
Family: IPFamily(netlinkFamily),
Address: testIPAddress,
Mask: "0",
},
},
}
got := convertInterface(linkAttrs, addrs)
assert.True(t, reflect.DeepEqual(expected, got),
"Got %+v\nExpected %+v", got, expected)
}
func TestConvertRoutes(t *testing.T) {
ip, ipNet, err := net.ParseCIDR(testIPAddressWithMask)
assert.Nil(t, err)
assert.NotNil(t, ipNet)
routes := []netlink.Route{
{
Dst: ipNet,
Src: ip,
Gw: ip,
LinkIndex: -1,
Scope: testScope,
},
}
expected := []Route{
{
Dest: testIPAddress,
Gateway: testIPAddress,
Source: testIPAddress,
Scope: uint32(testScope),
},
}
got := convertRoutes(routes)
assert.True(t, reflect.DeepEqual(expected, got),
"Got %+v\nExpected %+v", got, expected)
}
type testTeardownNetwork func()
func testSetupNetwork(t *testing.T) testTeardownNetwork {
skipUnlessRoot(t)
// new temporary namespace so we don't pollute the host
// lock thread since the namespace is thread local
runtime.LockOSThread()
var err error
ns, err := netns.New()
if err != nil {
t.Fatal("Failed to create newns", ns)
}
return func() {
ns.Close()
runtime.UnlockOSThread()
}
}
func testCreateDummyNetwork(t *testing.T, handler *netlink.Handle) (int, Interface) {
hwAddr, err := net.ParseMAC(testHwAddr)
assert.Nil(t, err)
link := &netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
MTU: testMTU,
TxQLen: testTxQLen,
Name: testIfaceName,
HardwareAddr: hwAddr,
},
}
err = handler.LinkAdd(link)
assert.Nil(t, err)
err = handler.LinkSetUp(link)
assert.Nil(t, err)
attrs := link.Attrs()
assert.NotNil(t, attrs)
iface := Interface{
Device: testIfaceName,
Name: testIfaceName,
Mtu: uint64(testMTU),
HwAddr: testHwAddr,
}
return attrs.Index, iface
}
func TestScanNetwork(t *testing.T) {
tearDownNetworkCb := testSetupNetwork(t)
defer tearDownNetworkCb()
handler, err := netlink.NewHandle(netlinkFamily)
assert.Nil(t, err)
assert.NotNil(t, handler)
defer handler.Delete()
idx, expected := testCreateDummyNetwork(t, handler)
n := &netmon{
netIfaces: make(map[int]Interface),
netHandler: handler,
}
err = n.scanNetwork()
assert.Nil(t, err)
assert.True(t, reflect.DeepEqual(expected, n.netIfaces[idx]),
"Got %+v\nExpected %+v", n.netIfaces[idx], expected)
}
func TestStoreDataToSend(t *testing.T) {
var got Interface
expected := Interface{
Device: testIfaceName,
Name: testIfaceName,
Mtu: uint64(testMTU),
HwAddr: testHwAddr,
}
n := &netmon{
sharedFile: filepath.Join(testStorageParentPath, testSharedFile),
}
err := os.MkdirAll(testStorageParentPath, storageDirPerm)
defer os.RemoveAll(testStorageParentPath)
assert.Nil(t, err)
err = n.storeDataToSend(expected)
assert.Nil(t, err)
// Check the file has been created, check the content, and delete it.
_, err = os.Stat(n.sharedFile)
assert.Nil(t, err)
byteArray, err := ioutil.ReadFile(n.sharedFile)
assert.Nil(t, err)
err = json.Unmarshal(byteArray, &got)
assert.Nil(t, err)
assert.True(t, reflect.DeepEqual(expected, got),
"Got %+v\nExpected %+v", got, expected)
}
func TestExecKataCmdSuccess(t *testing.T) {
trueBinPath, err := exec.LookPath("true")
assert.Nil(t, err)
assert.NotEmpty(t, trueBinPath)
params := netmonParams{
runtimePath: trueBinPath,
}
n := &netmon{
netmonParams: params,
sharedFile: filepath.Join(testStorageParentPath, testSharedFile),
}
err = os.MkdirAll(testStorageParentPath, storageDirPerm)
assert.Nil(t, err)
defer os.RemoveAll(testStorageParentPath)
file, err := os.Create(n.sharedFile)
assert.Nil(t, err)
assert.NotNil(t, file)
file.Close()
_, err = os.Stat(n.sharedFile)
assert.Nil(t, err)
err = n.execKataCmd("")
assert.Nil(t, err)
_, err = os.Stat(n.sharedFile)
assert.NotNil(t, err)
}
func TestExecKataCmdFailure(t *testing.T) {
falseBinPath, err := exec.LookPath("false")
assert.Nil(t, err)
assert.NotEmpty(t, falseBinPath)
params := netmonParams{
runtimePath: falseBinPath,
}
n := &netmon{
netmonParams: params,
}
err = n.execKataCmd("")
assert.NotNil(t, err)
}
func TestActionsCLI(t *testing.T) {
trueBinPath, err := exec.LookPath("true")
assert.Nil(t, err)
assert.NotEmpty(t, trueBinPath)
params := netmonParams{
runtimePath: trueBinPath,
}
n := &netmon{
netmonParams: params,
sharedFile: filepath.Join(testStorageParentPath, testSharedFile),
}
err = os.MkdirAll(testStorageParentPath, storageDirPerm)
assert.Nil(t, err)
defer os.RemoveAll(testStorageParentPath)
// Test addInterfaceCLI
err = n.addInterfaceCLI(Interface{})
assert.Nil(t, err)
// Test delInterfaceCLI
err = n.delInterfaceCLI(Interface{})
assert.Nil(t, err)
// Test updateRoutesCLI
err = n.updateRoutesCLI([]Route{})
assert.Nil(t, err)
tearDownNetworkCb := testSetupNetwork(t)
defer tearDownNetworkCb()
handler, err := netlink.NewHandle(netlinkFamily)
assert.Nil(t, err)
assert.NotNil(t, handler)
defer handler.Delete()
n.netHandler = handler
// Test updateRoutes
err = n.updateRoutes()
assert.Nil(t, err)
// Test handleRTMDelRoute
err = n.handleRTMDelRoute(netlink.RouteUpdate{})
assert.Nil(t, err)
}
func TestHandleRTMNewAddr(t *testing.T) {
n := &netmon{}
err := n.handleRTMNewAddr(netlink.LinkUpdate{})
assert.Nil(t, err)
}
func TestHandleRTMDelAddr(t *testing.T) {
n := &netmon{}
err := n.handleRTMDelAddr(netlink.LinkUpdate{})
assert.Nil(t, err)
}
func TestHandleRTMNewLink(t *testing.T) {
n := &netmon{}
ev := netlink.LinkUpdate{
Link: &netlink.Dummy{},
}
// LinkAttrs is nil
err := n.handleRTMNewLink(ev)
assert.Nil(t, err)
// Link name contains "kata" suffix
ev = netlink.LinkUpdate{
Link: &netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: "foo_kata",
},
},
}
err = n.handleRTMNewLink(ev)
assert.Nil(t, err)
// Interface already exist in list
n.netIfaces = make(map[int]Interface)
n.netIfaces[testIfaceIndex] = Interface{}
ev = netlink.LinkUpdate{
Link: &netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: "foo0",
},
},
}
ev.Index = testIfaceIndex
err = n.handleRTMNewLink(ev)
assert.Nil(t, err)
// Flags are not up and running
n.netIfaces = make(map[int]Interface)
ev = netlink.LinkUpdate{
Link: &netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: "foo0",
},
},
}
ev.Index = testIfaceIndex
err = n.handleRTMNewLink(ev)
assert.Nil(t, err)
// Invalid link
n.netIfaces = make(map[int]Interface)
ev = netlink.LinkUpdate{
Link: &netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: "foo0",
},
},
}
ev.Index = testIfaceIndex
ev.Flags = unix.IFF_UP | unix.IFF_RUNNING
handler, err := netlink.NewHandle(netlinkFamily)
assert.Nil(t, err)
assert.NotNil(t, handler)
defer handler.Delete()
n.netHandler = handler
err = n.handleRTMNewLink(ev)
assert.NotNil(t, err)
}
func TestHandleRTMDelLink(t *testing.T) {
n := &netmon{}
ev := netlink.LinkUpdate{
Link: &netlink.Dummy{},
}
// LinkAttrs is nil
err := n.handleRTMDelLink(ev)
assert.Nil(t, err)
// Link name contains "kata" suffix
ev = netlink.LinkUpdate{
Link: &netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: "foo_kata",
},
},
}
err = n.handleRTMDelLink(ev)
assert.Nil(t, err)
// Interface does not exist in list
n.netIfaces = make(map[int]Interface)
ev = netlink.LinkUpdate{
Link: &netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: "foo0",
},
},
}
ev.Index = testIfaceIndex
err = n.handleRTMDelLink(ev)
assert.Nil(t, err)
}
func TestHandleRTMNewRouteIfaceNotFound(t *testing.T) {
n := &netmon{
netIfaces: make(map[int]Interface),
}
err := n.handleRTMNewRoute(netlink.RouteUpdate{})
assert.Nil(t, err)
}
func TestHandleLinkEvent(t *testing.T) {
n := &netmon{}
ev := netlink.LinkUpdate{}
// Unknown event
err := n.handleLinkEvent(ev)
assert.Nil(t, err)
// DONE event
ev.Header.Type = unix.NLMSG_DONE
err = n.handleLinkEvent(ev)
assert.Nil(t, err)
// ERROR event
ev.Header.Type = unix.NLMSG_ERROR
err = n.handleLinkEvent(ev)
assert.NotNil(t, err)
// NEWADDR event
ev.Header.Type = unix.RTM_NEWADDR
err = n.handleLinkEvent(ev)
assert.Nil(t, err)
// DELADDR event
ev.Header.Type = unix.RTM_DELADDR
err = n.handleLinkEvent(ev)
assert.Nil(t, err)
// NEWLINK event
ev.Header.Type = unix.RTM_NEWLINK
ev.Link = &netlink.Dummy{}
err = n.handleLinkEvent(ev)
assert.Nil(t, err)
// DELLINK event
ev.Header.Type = unix.RTM_DELLINK
ev.Link = &netlink.Dummy{}
err = n.handleLinkEvent(ev)
assert.Nil(t, err)
}
func TestHandleRouteEvent(t *testing.T) {
n := &netmon{}
ev := netlink.RouteUpdate{}
// Unknown event
err := n.handleRouteEvent(ev)
assert.Nil(t, err)
// RTM_NEWROUTE event
ev.Type = unix.RTM_NEWROUTE
err = n.handleRouteEvent(ev)
assert.Nil(t, err)
trueBinPath, err := exec.LookPath("true")
assert.Nil(t, err)
assert.NotEmpty(t, trueBinPath)
n.runtimePath = trueBinPath
n.sharedFile = filepath.Join(testStorageParentPath, testSharedFile)
err = os.MkdirAll(testStorageParentPath, storageDirPerm)
assert.Nil(t, err)
defer os.RemoveAll(testStorageParentPath)
tearDownNetworkCb := testSetupNetwork(t)
defer tearDownNetworkCb()
handler, err := netlink.NewHandle(netlinkFamily)
assert.Nil(t, err)
assert.NotNil(t, handler)
defer handler.Delete()
n.netHandler = handler
// RTM_DELROUTE event
ev.Type = unix.RTM_DELROUTE
err = n.handleRouteEvent(ev)
assert.Nil(t, err)
}

View File

@ -8,6 +8,7 @@ package client
import (
"context"
"fmt"
"net"
"net/url"
"strconv"
@ -31,6 +32,7 @@ const (
)
var defaultDialTimeout = 15 * time.Second
var defaultCloseTimeout = 5 * time.Second
// AgentClient is an agent gRPC client connection wrapper for agentgrpc.AgentServiceClient
type AgentClient struct {
@ -45,7 +47,26 @@ type yamuxSessionStream struct {
}
func (y *yamuxSessionStream) Close() error {
return y.session.Close()
waitCh := y.session.CloseChan()
timeout := time.NewTimer(defaultCloseTimeout)
if err := y.Conn.Close(); err != nil {
return err
}
if err := y.session.Close(); err != nil {
return err
}
// block until session is really closed
select {
case <-waitCh:
timeout.Stop()
case <-timeout.C:
return fmt.Errorf("timeout waiting for session close")
}
return nil
}
type dialer func(string, time.Duration) (net.Conn, error)

View File

@ -1173,6 +1173,10 @@ type Interface struct {
IPAddresses []*IPAddress `protobuf:"bytes,3,rep,name=IPAddresses" json:"IPAddresses,omitempty"`
Mtu uint64 `protobuf:"varint,4,opt,name=mtu,proto3" json:"mtu,omitempty"`
HwAddr string `protobuf:"bytes,5,opt,name=hwAddr,proto3" json:"hwAddr,omitempty"`
// pciAddr is the PCI address in the format "bridgeAddr/deviceAddr".
// Here, bridgeAddr is the address at which the bridge is attached on the root bus,
// while deviceAddr is the address at which the network device is attached on the bridge.
PciAddr string `protobuf:"bytes,6,opt,name=pciAddr,proto3" json:"pciAddr,omitempty"`
}
func (m *Interface) Reset() { *m = Interface{} }
@ -1215,6 +1219,13 @@ func (m *Interface) GetHwAddr() string {
return ""
}
func (m *Interface) GetPciAddr() string {
if m != nil {
return m.PciAddr
}
return ""
}
type Interfaces struct {
Interfaces []*Interface `protobuf:"bytes,1,rep,name=Interfaces" json:"Interfaces,omitempty"`
}
@ -1382,6 +1393,8 @@ type OnlineCPUMemRequest struct {
Wait bool `protobuf:"varint,1,opt,name=wait,proto3" json:"wait,omitempty"`
// NbCpus specifies the number of CPUs that were added and the agent has to online.
NbCpus uint32 `protobuf:"varint,2,opt,name=nb_cpus,json=nbCpus,proto3" json:"nb_cpus,omitempty"`
// CpuOnly specifies whether only online CPU or not.
CpuOnly bool `protobuf:"varint,3,opt,name=cpu_only,json=cpuOnly,proto3" json:"cpu_only,omitempty"`
}
func (m *OnlineCPUMemRequest) Reset() { *m = OnlineCPUMemRequest{} }
@ -1403,6 +1416,13 @@ func (m *OnlineCPUMemRequest) GetNbCpus() uint32 {
return 0
}
func (m *OnlineCPUMemRequest) GetCpuOnly() bool {
if m != nil {
return m.CpuOnly
}
return false
}
type ReseedRandomDevRequest struct {
// Data specifies the random data used to reseed the guest crng.
Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
@ -3948,6 +3968,12 @@ func (m *Interface) MarshalTo(dAtA []byte) (int, error) {
i = encodeVarintAgent(dAtA, i, uint64(len(m.HwAddr)))
i += copy(dAtA[i:], m.HwAddr)
}
if len(m.PciAddr) > 0 {
dAtA[i] = 0x32
i++
i = encodeVarintAgent(dAtA, i, uint64(len(m.PciAddr)))
i += copy(dAtA[i:], m.PciAddr)
}
return i, nil
}
@ -4236,6 +4262,16 @@ func (m *OnlineCPUMemRequest) MarshalTo(dAtA []byte) (int, error) {
i++
i = encodeVarintAgent(dAtA, i, uint64(m.NbCpus))
}
if m.CpuOnly {
dAtA[i] = 0x18
i++
if m.CpuOnly {
dAtA[i] = 1
} else {
dAtA[i] = 0
}
i++
}
return i, nil
}
@ -5052,6 +5088,10 @@ func (m *Interface) Size() (n int) {
if l > 0 {
n += 1 + l + sovAgent(uint64(l))
}
l = len(m.PciAddr)
if l > 0 {
n += 1 + l + sovAgent(uint64(l))
}
return n
}
@ -5165,6 +5205,9 @@ func (m *OnlineCPUMemRequest) Size() (n int) {
if m.NbCpus != 0 {
n += 1 + sovAgent(uint64(m.NbCpus))
}
if m.CpuOnly {
n += 2
}
return n
}
@ -9782,6 +9825,35 @@ func (m *Interface) Unmarshal(dAtA []byte) error {
}
m.HwAddr = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 6:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field PciAddr", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowAgent
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthAgent
}
postIndex := iNdEx + intStringLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.PciAddr = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipAgent(dAtA[iNdEx:])
@ -10650,6 +10722,26 @@ func (m *OnlineCPUMemRequest) Unmarshal(dAtA []byte) error {
break
}
}
case 3:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field CpuOnly", wireType)
}
var v int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowAgent
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
v |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
m.CpuOnly = bool(v != 0)
default:
iNdEx = preIndex
skippy, err := skipAgent(dAtA[iNdEx:])
@ -11416,152 +11508,154 @@ var (
func init() { proto.RegisterFile("agent.proto", fileDescriptorAgent) }
var fileDescriptorAgent = []byte{
// 2352 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x58, 0x5f, 0x6f, 0x1c, 0x49,
0x11, 0x67, 0xff, 0xda, 0x5b, 0xbb, 0x6b, 0x7b, 0xdb, 0x8e, 0xb3, 0xb7, 0x39, 0x82, 0x6f, 0x02,
0x39, 0x73, 0x47, 0x1c, 0x9d, 0x13, 0xc1, 0x29, 0x51, 0x14, 0x62, 0xc7, 0x38, 0xe6, 0x2e, 0x64,
0x19, 0xc7, 0x0a, 0x12, 0x0f, 0xab, 0xf1, 0x4c, 0x7b, 0xdd, 0x97, 0x9d, 0xe9, 0xb9, 0xee, 0x1e,
0xdb, 0xcb, 0x49, 0x3c, 0xf2, 0x09, 0x78, 0xe5, 0x0b, 0x20, 0xde, 0xee, 0x2b, 0xf0, 0xc0, 0x23,
0x9f, 0x00, 0xa1, 0x7c, 0x01, 0x24, 0x3e, 0x01, 0xea, 0x7f, 0xf3, 0x67, 0xff, 0x38, 0xe0, 0xb3,
0xc4, 0xcb, 0xee, 0x54, 0x75, 0x75, 0xd5, 0xaf, 0xaa, 0xbb, 0xab, 0xab, 0x0b, 0x9a, 0xde, 0x10,
0x47, 0x62, 0x2b, 0x66, 0x54, 0x50, 0x54, 0x1d, 0xb2, 0xd8, 0xef, 0x35, 0xa8, 0x4f, 0x34, 0xa3,
0x77, 0x6b, 0x48, 0xe9, 0x70, 0x84, 0xef, 0x2b, 0xea, 0x38, 0x39, 0xb9, 0x8f, 0xc3, 0x58, 0x8c,
0xf5, 0xa0, 0xf3, 0xa7, 0x32, 0xac, 0xef, 0x32, 0xec, 0x09, 0xbc, 0x4b, 0x23, 0xe1, 0x91, 0x08,
0x33, 0x17, 0x7f, 0x9d, 0x60, 0x2e, 0xd0, 0x47, 0xd0, 0xf2, 0x2d, 0x6f, 0x40, 0x82, 0x6e, 0x69,
0xa3, 0xb4, 0xd9, 0x70, 0x9b, 0x29, 0xef, 0x20, 0x40, 0x37, 0x61, 0x01, 0x5f, 0x60, 0x5f, 0x8e,
0x96, 0xd5, 0x68, 0x5d, 0x92, 0x07, 0x01, 0xfa, 0x0c, 0x9a, 0x5c, 0x30, 0x12, 0x0d, 0x07, 0x09,
0xc7, 0xac, 0x5b, 0xd9, 0x28, 0x6d, 0x36, 0xb7, 0x57, 0xb6, 0x24, 0xb4, 0xad, 0x43, 0x35, 0x70,
0xc4, 0x31, 0x73, 0x81, 0xa7, 0xdf, 0xe8, 0x2e, 0x2c, 0x04, 0xf8, 0x8c, 0xf8, 0x98, 0x77, 0xab,
0x1b, 0x95, 0xcd, 0xe6, 0x76, 0x4b, 0x8b, 0x3f, 0x57, 0x4c, 0xd7, 0x0e, 0xa2, 0x1f, 0xc3, 0x22,
0x17, 0x94, 0x79, 0x43, 0xcc, 0xbb, 0x35, 0x25, 0xd8, 0xb6, 0x7a, 0x15, 0xd7, 0x4d, 0x87, 0xd1,
0x87, 0x50, 0x79, 0xb5, 0x7b, 0xd0, 0xad, 0x2b, 0xeb, 0x60, 0xa4, 0x62, 0xec, 0xbb, 0x92, 0x8d,
0xee, 0x40, 0x9b, 0x7b, 0x51, 0x70, 0x4c, 0x2f, 0x06, 0x31, 0x09, 0x22, 0xde, 0x5d, 0xd8, 0x28,
0x6d, 0x2e, 0xba, 0x2d, 0xc3, 0xec, 0x4b, 0x9e, 0xf3, 0x08, 0x6e, 0x1c, 0x0a, 0x8f, 0x89, 0x2b,
0x44, 0xc7, 0x39, 0x82, 0x75, 0x17, 0x87, 0xf4, 0xec, 0x4a, 0xa1, 0xed, 0xc2, 0x82, 0x20, 0x21,
0xa6, 0x89, 0x50, 0xa1, 0x6d, 0xbb, 0x96, 0x74, 0xfe, 0x52, 0x02, 0xb4, 0x77, 0x81, 0xfd, 0x3e,
0xa3, 0x3e, 0xe6, 0xfc, 0xff, 0xb4, 0x5c, 0x1f, 0xc3, 0x42, 0xac, 0x01, 0x74, 0xab, 0x4a, 0xdc,
0xac, 0x82, 0x45, 0x65, 0x47, 0x9d, 0xaf, 0x60, 0xed, 0x90, 0x0c, 0x23, 0x6f, 0x74, 0x8d, 0x78,
0xd7, 0xa1, 0xce, 0x95, 0x4e, 0x05, 0xb5, 0xed, 0x1a, 0xca, 0xe9, 0x03, 0x7a, 0xe3, 0x11, 0x71,
0x7d, 0x96, 0x9c, 0x7b, 0xb0, 0x5a, 0xd0, 0xc8, 0x63, 0x1a, 0x71, 0xac, 0x00, 0x08, 0x4f, 0x24,
0x5c, 0x29, 0xab, 0xb9, 0x86, 0x72, 0x30, 0xac, 0x7d, 0x49, 0xb8, 0x15, 0xc7, 0xff, 0x0b, 0x84,
0x75, 0xa8, 0x9f, 0x50, 0x16, 0x7a, 0xc2, 0x22, 0xd0, 0x14, 0x42, 0x50, 0xf5, 0xd8, 0x90, 0x77,
0x2b, 0x1b, 0x95, 0xcd, 0x86, 0xab, 0xbe, 0xe5, 0xae, 0x9c, 0x30, 0x63, 0x70, 0x7d, 0x04, 0x2d,
0x13, 0xf7, 0xc1, 0x88, 0x70, 0xa1, 0xec, 0xb4, 0xdc, 0xa6, 0xe1, 0xc9, 0x39, 0x0e, 0x85, 0xf5,
0xa3, 0x38, 0xb8, 0xe2, 0x81, 0xdf, 0x86, 0x06, 0xc3, 0x9c, 0x26, 0x4c, 0x1e, 0xd3, 0xb2, 0x5a,
0xf7, 0x35, 0xbd, 0xee, 0x5f, 0x92, 0x28, 0xb9, 0x70, 0xed, 0x98, 0x9b, 0x89, 0x99, 0x23, 0x24,
0xf8, 0x55, 0x8e, 0xd0, 0x23, 0xb8, 0xd1, 0xf7, 0x12, 0x7e, 0x15, 0xac, 0xce, 0x63, 0x79, 0xfc,
0x78, 0x12, 0x5e, 0x69, 0xf2, 0x9f, 0x4b, 0xb0, 0xb8, 0x1b, 0x27, 0x47, 0xdc, 0x1b, 0x62, 0xf4,
0x03, 0x68, 0x0a, 0x2a, 0xbc, 0xd1, 0x20, 0x91, 0xa4, 0x12, 0xaf, 0xba, 0xa0, 0x58, 0x5a, 0x40,
0x86, 0x1d, 0x33, 0x3f, 0x4e, 0x8c, 0x44, 0x79, 0xa3, 0xb2, 0x59, 0x75, 0x9b, 0x9a, 0xa7, 0x45,
0xb6, 0x60, 0x55, 0x8d, 0x0d, 0x48, 0x34, 0x78, 0x8b, 0x59, 0x84, 0x47, 0x21, 0x0d, 0xb0, 0xda,
0xbf, 0x55, 0xb7, 0xa3, 0x86, 0x0e, 0xa2, 0x2f, 0xd2, 0x01, 0xf4, 0x09, 0x74, 0x52, 0x79, 0x79,
0x28, 0x95, 0x74, 0x55, 0x49, 0x2f, 0x1b, 0xe9, 0x23, 0xc3, 0x76, 0x7e, 0x0f, 0x4b, 0xaf, 0x4f,
0x19, 0x15, 0x62, 0x44, 0xa2, 0xe1, 0x73, 0x4f, 0x78, 0x32, 0x7b, 0xc4, 0x98, 0x11, 0x1a, 0x70,
0x83, 0xd6, 0x92, 0xe8, 0x53, 0xe8, 0x08, 0x2d, 0x8b, 0x83, 0x81, 0x95, 0x29, 0x2b, 0x99, 0x95,
0x74, 0xa0, 0x6f, 0x84, 0x7f, 0x04, 0x4b, 0x99, 0xb0, 0xcc, 0x3f, 0x06, 0x6f, 0x3b, 0xe5, 0xbe,
0x26, 0x21, 0x76, 0xce, 0x54, 0xac, 0xd4, 0x22, 0xa3, 0x4f, 0xa1, 0x91, 0xc5, 0xa1, 0xa4, 0x76,
0xc8, 0x92, 0xde, 0x21, 0x36, 0x9c, 0xee, 0x62, 0x1a, 0x94, 0x27, 0xb0, 0x2c, 0x52, 0xe0, 0x83,
0xc0, 0x13, 0x5e, 0x71, 0x53, 0x15, 0xbd, 0x72, 0x97, 0x44, 0x81, 0x76, 0x1e, 0x43, 0xa3, 0x4f,
0x02, 0xae, 0x0d, 0x77, 0x61, 0xc1, 0x4f, 0x18, 0xc3, 0x91, 0xb0, 0x2e, 0x1b, 0x12, 0xad, 0x41,
0x6d, 0x44, 0x42, 0x22, 0x8c, 0x9b, 0x9a, 0x70, 0x28, 0xc0, 0x4b, 0x1c, 0x52, 0x36, 0x56, 0x01,
0x5b, 0x83, 0x5a, 0x7e, 0x71, 0x35, 0x81, 0x6e, 0x41, 0x23, 0xf4, 0x2e, 0xd2, 0x45, 0x95, 0x23,
0x8b, 0xa1, 0x77, 0xa1, 0xc1, 0x77, 0x61, 0xe1, 0xc4, 0x23, 0x23, 0x3f, 0x12, 0x26, 0x2a, 0x96,
0xcc, 0x0c, 0x56, 0xf3, 0x06, 0xff, 0x5a, 0x86, 0xa6, 0xb6, 0xa8, 0x01, 0xaf, 0x41, 0xcd, 0xf7,
0xfc, 0xd3, 0xd4, 0xa4, 0x22, 0xd0, 0x5d, 0x0b, 0xa4, 0x9c, 0x4f, 0xc2, 0x19, 0x52, 0x0b, 0xed,
0x3e, 0x00, 0x3f, 0xf7, 0x62, 0x83, 0xad, 0x32, 0x47, 0xb8, 0x21, 0x65, 0x34, 0xdc, 0x07, 0xd0,
0xd2, 0xfb, 0xce, 0x4c, 0xa9, 0xce, 0x99, 0xd2, 0xd4, 0x52, 0x7a, 0xd2, 0x1d, 0x68, 0x27, 0x1c,
0x0f, 0x4e, 0x09, 0x66, 0x1e, 0xf3, 0x4f, 0xc7, 0xdd, 0x9a, 0xbe, 0x23, 0x13, 0x8e, 0x5f, 0x58,
0x1e, 0xda, 0x86, 0x9a, 0x4c, 0x7f, 0xbc, 0x5b, 0x57, 0xd7, 0xf1, 0x87, 0x79, 0x95, 0xca, 0xd5,
0x2d, 0xf5, 0xbb, 0x17, 0x09, 0x36, 0x76, 0xb5, 0x68, 0xef, 0x73, 0x80, 0x8c, 0x89, 0x56, 0xa0,
0xf2, 0x16, 0x8f, 0xcd, 0x39, 0x94, 0x9f, 0x32, 0x38, 0x67, 0xde, 0x28, 0xb1, 0x51, 0xd7, 0xc4,
0xa3, 0xf2, 0xe7, 0x25, 0xc7, 0x87, 0xe5, 0x9d, 0xd1, 0x5b, 0x42, 0x73, 0xd3, 0xd7, 0xa0, 0x16,
0x7a, 0x5f, 0x51, 0x66, 0x23, 0xa9, 0x08, 0xc5, 0x25, 0x11, 0x65, 0x56, 0x85, 0x22, 0xd0, 0x12,
0x94, 0x69, 0xac, 0xe2, 0xd5, 0x70, 0xcb, 0x34, 0xce, 0x0c, 0x55, 0x73, 0x86, 0x9c, 0x7f, 0x54,
0x01, 0x32, 0x2b, 0xc8, 0x85, 0x1e, 0xa1, 0x03, 0x8e, 0x99, 0x2c, 0x41, 0x06, 0xc7, 0x63, 0x81,
0xf9, 0x80, 0x61, 0x3f, 0x61, 0x9c, 0x9c, 0xc9, 0xf5, 0x93, 0x6e, 0xdf, 0xd0, 0x6e, 0x4f, 0x60,
0x73, 0x6f, 0x12, 0x7a, 0xa8, 0xe7, 0xed, 0xc8, 0x69, 0xae, 0x9d, 0x85, 0x0e, 0xe0, 0x46, 0xa6,
0x33, 0xc8, 0xa9, 0x2b, 0x5f, 0xa6, 0x6e, 0x35, 0x55, 0x17, 0x64, 0xaa, 0xf6, 0x60, 0x95, 0xd0,
0xc1, 0xd7, 0x09, 0x4e, 0x0a, 0x8a, 0x2a, 0x97, 0x29, 0xea, 0x10, 0xfa, 0x6b, 0x35, 0x21, 0x53,
0xd3, 0x87, 0x0f, 0x72, 0x5e, 0xca, 0xe3, 0x9e, 0x53, 0x56, 0xbd, 0x4c, 0xd9, 0x7a, 0x8a, 0x4a,
0xe6, 0x83, 0x4c, 0xe3, 0x2f, 0x61, 0x9d, 0xd0, 0xc1, 0xb9, 0x47, 0xc4, 0xa4, 0xba, 0xda, 0x7b,
0x9c, 0x94, 0x97, 0x6e, 0x51, 0x97, 0x76, 0x32, 0xc4, 0x6c, 0x58, 0x70, 0xb2, 0xfe, 0x1e, 0x27,
0x5f, 0xaa, 0x09, 0x99, 0x9a, 0x67, 0xd0, 0x21, 0x74, 0x12, 0xcd, 0xc2, 0x65, 0x4a, 0x96, 0x09,
0x2d, 0x22, 0xd9, 0x81, 0x0e, 0xc7, 0xbe, 0xa0, 0x2c, 0xbf, 0x09, 0x16, 0x2f, 0x53, 0xb1, 0x62,
0xe4, 0x53, 0x1d, 0xce, 0x6f, 0xa1, 0xf5, 0x22, 0x19, 0x62, 0x31, 0x3a, 0x4e, 0x93, 0xc1, 0xb5,
0xe5, 0x1f, 0xe7, 0xdf, 0x65, 0x68, 0xee, 0x0e, 0x19, 0x4d, 0xe2, 0x42, 0x4e, 0xd6, 0x87, 0x74,
0x32, 0x27, 0x2b, 0x11, 0x95, 0x93, 0xb5, 0xf0, 0x43, 0x68, 0x85, 0xea, 0xe8, 0x1a, 0x79, 0x9d,
0x87, 0x3a, 0x53, 0x87, 0xda, 0x6d, 0x86, 0xb9, 0x64, 0xb6, 0x05, 0x10, 0x93, 0x80, 0x9b, 0x39,
0x3a, 0x1d, 0x2d, 0x9b, 0x8a, 0xd0, 0xa6, 0x68, 0xb7, 0x11, 0xa7, 0xd9, 0xfa, 0x33, 0x68, 0x1e,
0xcb, 0x20, 0x99, 0x09, 0x85, 0x64, 0x94, 0x45, 0xcf, 0x85, 0xe3, 0xec, 0x10, 0xbe, 0x80, 0xf6,
0xa9, 0x0e, 0x99, 0x99, 0xa4, 0xf7, 0xd0, 0x1d, 0xe3, 0x49, 0xe6, 0xef, 0x56, 0x3e, 0xb2, 0x7a,
0x01, 0x5a, 0xa7, 0x39, 0x56, 0xef, 0x10, 0x3a, 0x53, 0x22, 0x33, 0x72, 0xd0, 0x66, 0x3e, 0x07,
0x35, 0xb7, 0x91, 0x36, 0x94, 0x9f, 0x99, 0xcf, 0x4b, 0xbf, 0x82, 0xf5, 0xc9, 0x32, 0xc7, 0x14,
0x65, 0x0f, 0xa1, 0xe5, 0x2b, 0x74, 0x85, 0x15, 0xe8, 0x4c, 0xe1, 0x76, 0x9b, 0x7e, 0x46, 0x38,
0x01, 0xa0, 0x37, 0x8c, 0x08, 0x7c, 0x28, 0x18, 0xf6, 0xc2, 0xeb, 0xa8, 0x9a, 0x11, 0x54, 0xd5,
0x15, 0x5b, 0x51, 0x45, 0xa1, 0xfa, 0x76, 0x3e, 0x86, 0xd5, 0x82, 0x15, 0x03, 0x79, 0x05, 0x2a,
0x23, 0x1c, 0x29, 0xed, 0x6d, 0x57, 0x7e, 0x3a, 0x1e, 0x74, 0x5c, 0xec, 0x05, 0xd7, 0x87, 0xc6,
0x98, 0xa8, 0x64, 0x26, 0x36, 0x01, 0xe5, 0x4d, 0x18, 0x28, 0x16, 0x75, 0x29, 0x87, 0xfa, 0x15,
0x74, 0x76, 0x47, 0x94, 0xe3, 0x43, 0x11, 0x90, 0xe8, 0x3a, 0xca, 0xfc, 0x6f, 0x60, 0xf5, 0xb5,
0x18, 0xbf, 0x91, 0xca, 0x38, 0xf9, 0x1d, 0xbe, 0x26, 0xff, 0x18, 0x3d, 0xb7, 0xfe, 0x31, 0x7a,
0x2e, 0x2b, 0x7c, 0x9f, 0x8e, 0x92, 0x30, 0x52, 0xdb, 0xbd, 0xed, 0x1a, 0xca, 0xf9, 0xb6, 0x04,
0x6b, 0xfa, 0x0d, 0x7e, 0xa8, 0x9f, 0x9e, 0xd6, 0x7c, 0x0f, 0x16, 0x4f, 0x29, 0x17, 0x91, 0x17,
0x62, 0x63, 0x3a, 0xa5, 0xa5, 0x7a, 0xf9, 0x66, 0x2d, 0xab, 0x57, 0x81, 0xfc, 0x2c, 0x3c, 0x8c,
0x2b, 0x97, 0x3f, 0x8c, 0xa7, 0x9e, 0xbe, 0xd5, 0xe9, 0xa7, 0x2f, 0xfa, 0x3e, 0x80, 0x15, 0x22,
0x81, 0xba, 0xf8, 0x1b, 0x6e, 0xc3, 0x70, 0x0e, 0x02, 0xe7, 0x26, 0xdc, 0x78, 0x8e, 0xb9, 0x60,
0x74, 0x5c, 0x44, 0xed, 0x78, 0xd0, 0x38, 0xe8, 0x3f, 0x0b, 0x02, 0x86, 0x39, 0x47, 0x77, 0xa1,
0x7e, 0xe2, 0x85, 0x64, 0xa4, 0x0f, 0xd6, 0x92, 0xcd, 0x3b, 0x07, 0xfd, 0x5f, 0x28, 0xae, 0x6b,
0x46, 0x65, 0x32, 0xf3, 0xf4, 0x14, 0x13, 0x46, 0x4b, 0xca, 0xf5, 0x0f, 0x3d, 0xfe, 0xd6, 0x5c,
0xd9, 0xea, 0xdb, 0xf9, 0x63, 0x09, 0x1a, 0x07, 0x91, 0xc0, 0xec, 0xc4, 0xf3, 0xd5, 0x63, 0x4c,
0x37, 0x07, 0x4c, 0x90, 0x0c, 0x25, 0x67, 0xaa, 0xd0, 0x69, 0x85, 0xea, 0x5b, 0xe6, 0x9d, 0x14,
0x5c, 0x1a, 0xa7, 0x65, 0x0b, 0xca, 0x0c, 0xb8, 0x79, 0x19, 0x19, 0xe9, 0x50, 0x24, 0xa6, 0x3e,
0x90, 0x9f, 0xd2, 0xe0, 0xe9, 0xb9, 0x14, 0x30, 0x51, 0x31, 0x94, 0xf3, 0x04, 0x20, 0x45, 0xc5,
0x65, 0x85, 0x96, 0x51, 0xa6, 0x48, 0xb0, 0x96, 0x2c, 0xdf, 0xcd, 0x89, 0x38, 0xdf, 0x40, 0xcd,
0xa5, 0x89, 0xd0, 0x5b, 0x1e, 0x9b, 0xd7, 0x5b, 0xc3, 0x55, 0xdf, 0x32, 0x40, 0x43, 0x4f, 0xe0,
0x73, 0x6f, 0x6c, 0x03, 0x64, 0xc8, 0x9c, 0xfb, 0x95, 0x82, 0xfb, 0xf2, 0x8d, 0xaa, 0x9e, 0x60,
0x0a, 0x7a, 0xc3, 0x35, 0x94, 0xbc, 0x6a, 0xb8, 0x4f, 0x63, 0xac, 0xc0, 0xb7, 0x5d, 0x4d, 0x38,
0xf7, 0xa0, 0xae, 0x8c, 0xcb, 0xcd, 0x61, 0xbe, 0x0c, 0xe6, 0xa6, 0xc6, 0xac, 0x78, 0xae, 0x19,
0x72, 0xf6, 0xed, 0x2b, 0x32, 0x73, 0xc5, 0x6c, 0xda, 0x7b, 0xd0, 0x20, 0x96, 0x67, 0x52, 0xdd,
0x94, 0xd7, 0x99, 0x84, 0xf3, 0x1c, 0x56, 0x9f, 0x05, 0xc1, 0x77, 0xd5, 0xb2, 0x6f, 0x5b, 0x2d,
0xdf, 0x55, 0xd1, 0x63, 0x58, 0xd5, 0x7e, 0x69, 0x3f, 0xad, 0x96, 0x1f, 0x42, 0x9d, 0xd9, 0x98,
0x94, 0xb2, 0xde, 0x94, 0x11, 0x32, 0x63, 0xf2, 0x48, 0xc8, 0x27, 0x76, 0xb6, 0xa4, 0xf6, 0x48,
0xac, 0x42, 0x47, 0x0e, 0x14, 0x74, 0x3a, 0x3b, 0xb0, 0xfa, 0x2a, 0x1a, 0x91, 0x08, 0xef, 0xf6,
0x8f, 0x5e, 0xe2, 0x34, 0xa7, 0x22, 0xa8, 0xca, 0x82, 0x49, 0x19, 0x5a, 0x74, 0xd5, 0xb7, 0x4c,
0x32, 0xd1, 0xf1, 0xc0, 0x8f, 0x13, 0x6e, 0x9a, 0x41, 0xf5, 0xe8, 0x78, 0x37, 0x4e, 0xb8, 0xf3,
0x13, 0xf5, 0xc6, 0xc5, 0x38, 0x70, 0xbd, 0x28, 0xa0, 0xe1, 0x73, 0x7c, 0x96, 0x53, 0x93, 0xbe,
0xa7, 0x6c, 0xda, 0xfc, 0xb6, 0x04, 0x0b, 0x26, 0x19, 0xa8, 0x5d, 0xc3, 0xc8, 0x19, 0x66, 0xe9,
0xa1, 0x51, 0x94, 0x7c, 0xf2, 0xe9, 0xaf, 0x01, 0x8d, 0x05, 0xa1, 0x69, 0x8a, 0x69, 0x6b, 0xee,
0x2b, 0xcd, 0xcc, 0x6d, 0xae, 0x4a, 0x61, 0x73, 0xad, 0x43, 0xfd, 0x84, 0x8b, 0x71, 0x9c, 0x6e,
0x3a, 0x4d, 0xc9, 0xed, 0x6b, 0xf5, 0xd5, 0x94, 0x3e, 0x4b, 0xca, 0xc7, 0x75, 0x48, 0x93, 0x48,
0x0c, 0x62, 0x4a, 0x22, 0xa1, 0x9a, 0x75, 0x0d, 0x17, 0x14, 0xab, 0x2f, 0x39, 0xce, 0x1f, 0x4a,
0x50, 0xd7, 0x4d, 0x40, 0x59, 0xbc, 0xa7, 0x59, 0xb8, 0x4c, 0xd4, 0x8d, 0xa6, 0x6c, 0x99, 0x13,
0xae, 0x2c, 0xdd, 0x84, 0x85, 0xb3, 0x70, 0x10, 0x7b, 0xe2, 0xd4, 0x42, 0x3b, 0x0b, 0xfb, 0x9e,
0x38, 0x95, 0x9e, 0x65, 0xc9, 0x5c, 0x8d, 0x6b, 0x88, 0xed, 0x94, 0xab, 0xc4, 0xe6, 0x22, 0x75,
0x7e, 0x23, 0xdf, 0x2c, 0x69, 0x03, 0x6c, 0x05, 0x2a, 0x49, 0x0a, 0x46, 0x7e, 0x4a, 0xce, 0x30,
0xbd, 0x06, 0xe4, 0x27, 0xba, 0x0b, 0x4b, 0x5e, 0x10, 0x10, 0x39, 0xdd, 0x1b, 0xed, 0x93, 0xc0,
0x76, 0x71, 0x26, 0xb8, 0x9f, 0xf4, 0x60, 0xd1, 0x66, 0x44, 0x54, 0x87, 0xf2, 0xd9, 0xc3, 0x95,
0xef, 0xa9, 0xff, 0x9f, 0xae, 0x94, 0xb6, 0xff, 0xd5, 0x86, 0xd6, 0xb3, 0x21, 0x8e, 0x84, 0xa9,
0xb0, 0xd1, 0x3e, 0x2c, 0x4f, 0x74, 0x6c, 0x91, 0x79, 0x72, 0xcd, 0x6e, 0xe4, 0xf6, 0xd6, 0xb7,
0x74, 0x07, 0x78, 0xcb, 0x76, 0x80, 0xb7, 0xf6, 0xc2, 0x58, 0x8c, 0xd1, 0x1e, 0x2c, 0x15, 0x7b,
0x9b, 0xe8, 0x96, 0xbd, 0x30, 0x66, 0x74, 0x3c, 0xe7, 0xaa, 0xd9, 0x87, 0xe5, 0x89, 0x36, 0xa7,
0xc5, 0x33, 0xbb, 0xfb, 0x39, 0x57, 0xd1, 0x53, 0x68, 0xe6, 0xfa, 0x9a, 0xa8, 0xab, 0x95, 0x4c,
0xb7, 0x3a, 0xe7, 0x2a, 0xd8, 0x85, 0x76, 0xa1, 0xd5, 0x88, 0x7a, 0xc6, 0x9f, 0x19, 0xfd, 0xc7,
0xb9, 0x4a, 0x76, 0xa0, 0x99, 0xeb, 0xf8, 0x59, 0x14, 0xd3, 0x6d, 0xc5, 0xde, 0x07, 0x33, 0x46,
0x4c, 0xcd, 0xf2, 0x02, 0xda, 0x85, 0xfe, 0x9c, 0x05, 0x32, 0xab, 0x37, 0xd8, 0xbb, 0x35, 0x73,
0xcc, 0x68, 0xda, 0x87, 0xe5, 0x89, 0x6e, 0x9d, 0x0d, 0xee, 0xec, 0x26, 0xde, 0x5c, 0xb7, 0xbe,
0x50, 0x8b, 0x9d, 0x2b, 0x4f, 0x73, 0x8b, 0x3d, 0xdd, 0x9b, 0xeb, 0x7d, 0x38, 0x7b, 0xd0, 0xa0,
0xda, 0x83, 0xa5, 0x62, 0x5b, 0xce, 0x2a, 0x9b, 0xd9, 0xac, 0xbb, 0x7c, 0xe7, 0x14, 0x3a, 0x74,
0xd9, 0xce, 0x99, 0xd5, 0xb8, 0x9b, 0xab, 0xe8, 0x19, 0x80, 0xa9, 0x62, 0x03, 0x12, 0xa5, 0x4b,
0x36, 0x55, 0x3d, 0xa7, 0x4b, 0x36, 0xa3, 0xe2, 0x7d, 0x0a, 0xa0, 0x8b, 0xcf, 0x80, 0x26, 0x02,
0xdd, 0xb4, 0x30, 0x26, 0x2a, 0xde, 0x5e, 0x77, 0x7a, 0x60, 0x4a, 0x01, 0x66, 0xec, 0x2a, 0x0a,
0x9e, 0x00, 0x64, 0x45, 0xad, 0x55, 0x30, 0x55, 0xe6, 0x5e, 0x12, 0x83, 0x56, 0xbe, 0x84, 0x45,
0xc6, 0xd7, 0x19, 0x65, 0xed, 0x5c, 0x15, 0x8f, 0xa0, 0x95, 0xbf, 0x8b, 0xad, 0x8a, 0x19, 0xf7,
0x73, 0x6f, 0xf2, 0x0e, 0x45, 0x3f, 0xb7, 0x1b, 0x35, 0x63, 0x15, 0x36, 0xea, 0x7f, 0xa5, 0x61,
0xe2, 0x0e, 0x2f, 0xe6, 0x91, 0xf7, 0x6b, 0xf8, 0x19, 0xb4, 0xf2, 0x97, 0xb7, 0xc5, 0x3f, 0xe3,
0x42, 0xef, 0x15, 0x2e, 0x70, 0xf4, 0x14, 0x96, 0x8a, 0x17, 0x37, 0xca, 0x1d, 0xca, 0xa9, 0xeb,
0xbc, 0xb7, 0x32, 0x61, 0x98, 0xa3, 0x07, 0x00, 0xd9, 0x05, 0x6f, 0xd7, 0x6e, 0xea, 0xca, 0x9f,
0xb0, 0xba, 0x0b, 0xed, 0x42, 0xd9, 0x6f, 0xb3, 0xc4, 0xac, 0xb7, 0xc0, 0x65, 0x49, 0xbc, 0x58,
0x86, 0x5b, 0xe8, 0x33, 0x8b, 0xf3, 0xcb, 0x76, 0x4f, 0xbe, 0x18, 0xb1, 0xa1, 0x9b, 0x51, 0xa0,
0xbc, 0xe7, 0x34, 0xe7, 0x6b, 0x91, 0xdc, 0x69, 0x9e, 0x51, 0xa2, 0xcc, 0x53, 0xb4, 0xd3, 0xfa,
0xdb, 0xbb, 0xdb, 0xa5, 0xbf, 0xbf, 0xbb, 0x5d, 0xfa, 0xe7, 0xbb, 0xdb, 0xa5, 0xe3, 0xba, 0x1a,
0x7d, 0xf0, 0x9f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x2b, 0xc8, 0x6c, 0x30, 0xe5, 0x1c, 0x00, 0x00,
// 2381 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x58, 0xdb, 0x6e, 0x1b, 0xc9,
0xd1, 0xfe, 0x79, 0x10, 0x29, 0x16, 0x49, 0x49, 0x6c, 0xc9, 0x32, 0x4d, 0xfb, 0x77, 0xb4, 0xe3,
0xc4, 0xab, 0xec, 0xc6, 0x32, 0x56, 0x36, 0x92, 0x85, 0x0d, 0xc3, 0xb1, 0x65, 0x45, 0x56, 0x76,
0x1d, 0x33, 0x23, 0x0b, 0x0e, 0x10, 0x04, 0xc4, 0x68, 0xa6, 0x45, 0xf5, 0x9a, 0x33, 0x3d, 0xdb,
0xdd, 0x23, 0x89, 0x59, 0x20, 0x97, 0x79, 0x8b, 0xbc, 0x40, 0x10, 0xe4, 0x66, 0x5f, 0x21, 0x17,
0xb9, 0xcc, 0x13, 0x04, 0x81, 0x5f, 0x20, 0x40, 0x9e, 0x20, 0xe8, 0xd3, 0x1c, 0x78, 0x90, 0x13,
0xad, 0x80, 0xdc, 0x90, 0x53, 0xd5, 0xd5, 0x55, 0x5f, 0x55, 0x77, 0x57, 0x57, 0x17, 0x34, 0xbd,
0x21, 0x8e, 0xc4, 0x56, 0xcc, 0xa8, 0xa0, 0xa8, 0x3a, 0x64, 0xb1, 0xdf, 0x6b, 0x50, 0x9f, 0x68,
0x46, 0xef, 0xe6, 0x90, 0xd2, 0xe1, 0x08, 0xdf, 0x57, 0xd4, 0x51, 0x72, 0x7c, 0x1f, 0x87, 0xb1,
0x18, 0xeb, 0x41, 0xe7, 0x0f, 0x65, 0x58, 0xdf, 0x61, 0xd8, 0x13, 0x78, 0x87, 0x46, 0xc2, 0x23,
0x11, 0x66, 0x2e, 0xfe, 0x3a, 0xc1, 0x5c, 0xa0, 0x8f, 0xa0, 0xe5, 0x5b, 0xde, 0x80, 0x04, 0xdd,
0xd2, 0x46, 0x69, 0xb3, 0xe1, 0x36, 0x53, 0xde, 0x7e, 0x80, 0xae, 0x43, 0x1d, 0x9f, 0x63, 0x5f,
0x8e, 0x96, 0xd5, 0x68, 0x4d, 0x92, 0xfb, 0x01, 0xfa, 0x0c, 0x9a, 0x5c, 0x30, 0x12, 0x0d, 0x07,
0x09, 0xc7, 0xac, 0x5b, 0xd9, 0x28, 0x6d, 0x36, 0xb7, 0x57, 0xb6, 0x24, 0xb4, 0xad, 0x03, 0x35,
0x70, 0xc8, 0x31, 0x73, 0x81, 0xa7, 0xdf, 0xe8, 0x2e, 0xd4, 0x03, 0x7c, 0x4a, 0x7c, 0xcc, 0xbb,
0xd5, 0x8d, 0xca, 0x66, 0x73, 0xbb, 0xa5, 0xc5, 0x5f, 0x28, 0xa6, 0x6b, 0x07, 0xd1, 0x0f, 0x61,
0x91, 0x0b, 0xca, 0xbc, 0x21, 0xe6, 0xdd, 0x05, 0x25, 0xd8, 0xb6, 0x7a, 0x15, 0xd7, 0x4d, 0x87,
0xd1, 0x2d, 0xa8, 0xbc, 0xde, 0xd9, 0xef, 0xd6, 0x94, 0x75, 0x30, 0x52, 0x31, 0xf6, 0x5d, 0xc9,
0x46, 0x77, 0xa0, 0xcd, 0xbd, 0x28, 0x38, 0xa2, 0xe7, 0x83, 0x98, 0x04, 0x11, 0xef, 0xd6, 0x37,
0x4a, 0x9b, 0x8b, 0x6e, 0xcb, 0x30, 0xfb, 0x92, 0xe7, 0x3c, 0x82, 0x6b, 0x07, 0xc2, 0x63, 0xe2,
0x12, 0xd1, 0x71, 0x0e, 0x61, 0xdd, 0xc5, 0x21, 0x3d, 0xbd, 0x54, 0x68, 0xbb, 0x50, 0x17, 0x24,
0xc4, 0x34, 0x11, 0x2a, 0xb4, 0x6d, 0xd7, 0x92, 0xce, 0x9f, 0x4a, 0x80, 0x76, 0xcf, 0xb1, 0xdf,
0x67, 0xd4, 0xc7, 0x9c, 0xff, 0x8f, 0x96, 0xeb, 0x63, 0xa8, 0xc7, 0x1a, 0x40, 0xb7, 0xaa, 0xc4,
0xcd, 0x2a, 0x58, 0x54, 0x76, 0xd4, 0xf9, 0x0a, 0xd6, 0x0e, 0xc8, 0x30, 0xf2, 0x46, 0x57, 0x88,
0x77, 0x1d, 0x6a, 0x5c, 0xe9, 0x54, 0x50, 0xdb, 0xae, 0xa1, 0x9c, 0x3e, 0xa0, 0xb7, 0x1e, 0x11,
0x57, 0x67, 0xc9, 0xb9, 0x07, 0xab, 0x05, 0x8d, 0x3c, 0xa6, 0x11, 0xc7, 0x0a, 0x80, 0xf0, 0x44,
0xc2, 0x95, 0xb2, 0x05, 0xd7, 0x50, 0x0e, 0x86, 0xb5, 0x2f, 0x09, 0xb7, 0xe2, 0xf8, 0xbf, 0x81,
0xb0, 0x0e, 0xb5, 0x63, 0xca, 0x42, 0x4f, 0x58, 0x04, 0x9a, 0x42, 0x08, 0xaa, 0x1e, 0x1b, 0xf2,
0x6e, 0x65, 0xa3, 0xb2, 0xd9, 0x70, 0xd5, 0xb7, 0xdc, 0x95, 0x13, 0x66, 0x0c, 0xae, 0x8f, 0xa0,
0x65, 0xe2, 0x3e, 0x18, 0x11, 0x2e, 0x94, 0x9d, 0x96, 0xdb, 0x34, 0x3c, 0x39, 0xc7, 0xa1, 0xb0,
0x7e, 0x18, 0x07, 0x97, 0x3c, 0xf0, 0xdb, 0xd0, 0x60, 0x98, 0xd3, 0x84, 0xc9, 0x63, 0x5a, 0x56,
0xeb, 0xbe, 0xa6, 0xd7, 0xfd, 0x4b, 0x12, 0x25, 0xe7, 0xae, 0x1d, 0x73, 0x33, 0x31, 0x73, 0x84,
0x04, 0xbf, 0xcc, 0x11, 0x7a, 0x04, 0xd7, 0xfa, 0x5e, 0xc2, 0x2f, 0x83, 0xd5, 0x79, 0x2c, 0x8f,
0x1f, 0x4f, 0xc2, 0x4b, 0x4d, 0xfe, 0x63, 0x09, 0x16, 0x77, 0xe2, 0xe4, 0x90, 0x7b, 0x43, 0x8c,
0xbe, 0x07, 0x4d, 0x41, 0x85, 0x37, 0x1a, 0x24, 0x92, 0x54, 0xe2, 0x55, 0x17, 0x14, 0x4b, 0x0b,
0xc8, 0xb0, 0x63, 0xe6, 0xc7, 0x89, 0x91, 0x28, 0x6f, 0x54, 0x36, 0xab, 0x6e, 0x53, 0xf3, 0xb4,
0xc8, 0x16, 0xac, 0xaa, 0xb1, 0x01, 0x89, 0x06, 0xef, 0x30, 0x8b, 0xf0, 0x28, 0xa4, 0x01, 0x56,
0xfb, 0xb7, 0xea, 0x76, 0xd4, 0xd0, 0x7e, 0xf4, 0x45, 0x3a, 0x80, 0x3e, 0x81, 0x4e, 0x2a, 0x2f,
0x0f, 0xa5, 0x92, 0xae, 0x2a, 0xe9, 0x65, 0x23, 0x7d, 0x68, 0xd8, 0xce, 0xef, 0x60, 0xe9, 0xcd,
0x09, 0xa3, 0x42, 0x8c, 0x48, 0x34, 0x7c, 0xe1, 0x09, 0x4f, 0x66, 0x8f, 0x18, 0x33, 0x42, 0x03,
0x6e, 0xd0, 0x5a, 0x12, 0x7d, 0x0a, 0x1d, 0xa1, 0x65, 0x71, 0x30, 0xb0, 0x32, 0x65, 0x25, 0xb3,
0x92, 0x0e, 0xf4, 0x8d, 0xf0, 0x0f, 0x60, 0x29, 0x13, 0x96, 0xf9, 0xc7, 0xe0, 0x6d, 0xa7, 0xdc,
0x37, 0x24, 0xc4, 0xce, 0xa9, 0x8a, 0x95, 0x5a, 0x64, 0xf4, 0x29, 0x34, 0xb2, 0x38, 0x94, 0xd4,
0x0e, 0x59, 0xd2, 0x3b, 0xc4, 0x86, 0xd3, 0x5d, 0x4c, 0x83, 0xf2, 0x04, 0x96, 0x45, 0x0a, 0x7c,
0x10, 0x78, 0xc2, 0x2b, 0x6e, 0xaa, 0xa2, 0x57, 0xee, 0x92, 0x28, 0xd0, 0xce, 0x63, 0x68, 0xf4,
0x49, 0xc0, 0xb5, 0xe1, 0x2e, 0xd4, 0xfd, 0x84, 0x31, 0x1c, 0x09, 0xeb, 0xb2, 0x21, 0xd1, 0x1a,
0x2c, 0x8c, 0x48, 0x48, 0x84, 0x71, 0x53, 0x13, 0x0e, 0x05, 0x78, 0x85, 0x43, 0xca, 0xc6, 0x2a,
0x60, 0x6b, 0xb0, 0x90, 0x5f, 0x5c, 0x4d, 0xa0, 0x9b, 0xd0, 0x08, 0xbd, 0xf3, 0x74, 0x51, 0xe5,
0xc8, 0x62, 0xe8, 0x9d, 0x6b, 0xf0, 0x5d, 0xa8, 0x1f, 0x7b, 0x64, 0xe4, 0x47, 0xc2, 0x44, 0xc5,
0x92, 0x99, 0xc1, 0x6a, 0xde, 0xe0, 0x5f, 0xca, 0xd0, 0xd4, 0x16, 0x35, 0xe0, 0x35, 0x58, 0xf0,
0x3d, 0xff, 0x24, 0x35, 0xa9, 0x08, 0x74, 0xd7, 0x02, 0x29, 0xe7, 0x93, 0x70, 0x86, 0xd4, 0x42,
0xbb, 0x0f, 0xc0, 0xcf, 0xbc, 0xd8, 0x60, 0xab, 0xcc, 0x11, 0x6e, 0x48, 0x19, 0x0d, 0xf7, 0x01,
0xb4, 0xf4, 0xbe, 0x33, 0x53, 0xaa, 0x73, 0xa6, 0x34, 0xb5, 0x94, 0x9e, 0x74, 0x07, 0xda, 0x09,
0xc7, 0x83, 0x13, 0x82, 0x99, 0xc7, 0xfc, 0x93, 0x71, 0x77, 0x41, 0xdf, 0x91, 0x09, 0xc7, 0x2f,
0x2d, 0x0f, 0x6d, 0xc3, 0x82, 0x4c, 0x7f, 0xbc, 0x5b, 0x53, 0xd7, 0xf1, 0xad, 0xbc, 0x4a, 0xe5,
0xea, 0x96, 0xfa, 0xdd, 0x8d, 0x04, 0x1b, 0xbb, 0x5a, 0xb4, 0xf7, 0x39, 0x40, 0xc6, 0x44, 0x2b,
0x50, 0x79, 0x87, 0xc7, 0xe6, 0x1c, 0xca, 0x4f, 0x19, 0x9c, 0x53, 0x6f, 0x94, 0xd8, 0xa8, 0x6b,
0xe2, 0x51, 0xf9, 0xf3, 0x92, 0xe3, 0xc3, 0xf2, 0xf3, 0xd1, 0x3b, 0x42, 0x73, 0xd3, 0xd7, 0x60,
0x21, 0xf4, 0xbe, 0xa2, 0xcc, 0x46, 0x52, 0x11, 0x8a, 0x4b, 0x22, 0xca, 0xac, 0x0a, 0x45, 0xa0,
0x25, 0x28, 0xd3, 0x58, 0xc5, 0xab, 0xe1, 0x96, 0x69, 0x9c, 0x19, 0xaa, 0xe6, 0x0c, 0x39, 0x7f,
0xaf, 0x02, 0x64, 0x56, 0x90, 0x0b, 0x3d, 0x42, 0x07, 0x1c, 0x33, 0x59, 0x82, 0x0c, 0x8e, 0xc6,
0x02, 0xf3, 0x01, 0xc3, 0x7e, 0xc2, 0x38, 0x39, 0x95, 0xeb, 0x27, 0xdd, 0xbe, 0xa6, 0xdd, 0x9e,
0xc0, 0xe6, 0x5e, 0x27, 0xf4, 0x40, 0xcf, 0x7b, 0x2e, 0xa7, 0xb9, 0x76, 0x16, 0xda, 0x87, 0x6b,
0x99, 0xce, 0x20, 0xa7, 0xae, 0x7c, 0x91, 0xba, 0xd5, 0x54, 0x5d, 0x90, 0xa9, 0xda, 0x85, 0x55,
0x42, 0x07, 0x5f, 0x27, 0x38, 0x29, 0x28, 0xaa, 0x5c, 0xa4, 0xa8, 0x43, 0xe8, 0x2f, 0xd5, 0x84,
0x4c, 0x4d, 0x1f, 0x6e, 0xe4, 0xbc, 0x94, 0xc7, 0x3d, 0xa7, 0xac, 0x7a, 0x91, 0xb2, 0xf5, 0x14,
0x95, 0xcc, 0x07, 0x99, 0xc6, 0x9f, 0xc3, 0x3a, 0xa1, 0x83, 0x33, 0x8f, 0x88, 0x49, 0x75, 0x0b,
0x1f, 0x70, 0x52, 0x5e, 0xba, 0x45, 0x5d, 0xda, 0xc9, 0x10, 0xb3, 0x61, 0xc1, 0xc9, 0xda, 0x07,
0x9c, 0x7c, 0xa5, 0x26, 0x64, 0x6a, 0x9e, 0x41, 0x87, 0xd0, 0x49, 0x34, 0xf5, 0x8b, 0x94, 0x2c,
0x13, 0x5a, 0x44, 0xf2, 0x1c, 0x3a, 0x1c, 0xfb, 0x82, 0xb2, 0xfc, 0x26, 0x58, 0xbc, 0x48, 0xc5,
0x8a, 0x91, 0x4f, 0x75, 0x38, 0xbf, 0x86, 0xd6, 0xcb, 0x64, 0x88, 0xc5, 0xe8, 0x28, 0x4d, 0x06,
0x57, 0x96, 0x7f, 0x9c, 0x7f, 0x95, 0xa1, 0xb9, 0x33, 0x64, 0x34, 0x89, 0x0b, 0x39, 0x59, 0x1f,
0xd2, 0xc9, 0x9c, 0xac, 0x44, 0x54, 0x4e, 0xd6, 0xc2, 0x0f, 0xa1, 0x15, 0xaa, 0xa3, 0x6b, 0xe4,
0x75, 0x1e, 0xea, 0x4c, 0x1d, 0x6a, 0xb7, 0x19, 0xe6, 0x92, 0xd9, 0x16, 0x40, 0x4c, 0x02, 0x6e,
0xe6, 0xe8, 0x74, 0xb4, 0x6c, 0x2a, 0x42, 0x9b, 0xa2, 0xdd, 0x46, 0x9c, 0x66, 0xeb, 0xcf, 0xa0,
0x79, 0x24, 0x83, 0x64, 0x26, 0x14, 0x92, 0x51, 0x16, 0x3d, 0x17, 0x8e, 0xb2, 0x43, 0xf8, 0x12,
0xda, 0x27, 0x3a, 0x64, 0x66, 0x92, 0xde, 0x43, 0x77, 0x8c, 0x27, 0x99, 0xbf, 0x5b, 0xf9, 0xc8,
0xea, 0x05, 0x68, 0x9d, 0xe4, 0x58, 0xbd, 0x03, 0xe8, 0x4c, 0x89, 0xcc, 0xc8, 0x41, 0x9b, 0xf9,
0x1c, 0xd4, 0xdc, 0x46, 0xda, 0x50, 0x7e, 0x66, 0x3e, 0x2f, 0xfd, 0x02, 0xd6, 0x27, 0xcb, 0x1c,
0x53, 0x94, 0x3d, 0x84, 0x96, 0xaf, 0xd0, 0x15, 0x56, 0xa0, 0x33, 0x85, 0xdb, 0x6d, 0xfa, 0x19,
0xe1, 0x04, 0x80, 0xde, 0x32, 0x22, 0xf0, 0x81, 0x60, 0xd8, 0x0b, 0xaf, 0xa2, 0x6a, 0x46, 0x50,
0x55, 0x57, 0x6c, 0x45, 0x15, 0x85, 0xea, 0xdb, 0xf9, 0x18, 0x56, 0x0b, 0x56, 0x0c, 0xe4, 0x15,
0xa8, 0x8c, 0x70, 0xa4, 0xb4, 0xb7, 0x5d, 0xf9, 0xe9, 0x78, 0xd0, 0x71, 0xb1, 0x17, 0x5c, 0x1d,
0x1a, 0x63, 0xa2, 0x92, 0x99, 0xd8, 0x04, 0x94, 0x37, 0x61, 0xa0, 0x58, 0xd4, 0xa5, 0x1c, 0xea,
0xd7, 0xd0, 0xd9, 0x19, 0x51, 0x8e, 0x0f, 0x44, 0x40, 0xa2, 0xab, 0x28, 0xf3, 0xbf, 0x81, 0xd5,
0x37, 0x62, 0xfc, 0x56, 0x2a, 0xe3, 0xe4, 0xb7, 0xf8, 0x8a, 0xfc, 0x63, 0xf4, 0xcc, 0xfa, 0xc7,
0xe8, 0x99, 0xac, 0xf0, 0x7d, 0x3a, 0x4a, 0xc2, 0x48, 0x6d, 0xf7, 0xb6, 0x6b, 0x28, 0xe7, 0xdb,
0x12, 0xac, 0xe9, 0x37, 0xf8, 0x81, 0x7e, 0x7a, 0x5a, 0xf3, 0x3d, 0x58, 0x3c, 0xa1, 0x5c, 0x44,
0x5e, 0x88, 0x8d, 0xe9, 0x94, 0x96, 0xea, 0xe5, 0x9b, 0xb5, 0xac, 0x5e, 0x05, 0xf2, 0xb3, 0xf0,
0x30, 0xae, 0x5c, 0xfc, 0x30, 0x9e, 0x7a, 0xfa, 0x56, 0xa7, 0x9f, 0xbe, 0xe8, 0xff, 0x01, 0xac,
0x10, 0x09, 0xd4, 0xc5, 0xdf, 0x70, 0x1b, 0x86, 0xb3, 0x1f, 0x38, 0xd7, 0xe1, 0xda, 0x0b, 0xcc,
0x05, 0xa3, 0xe3, 0x22, 0x6a, 0xc7, 0x83, 0xc6, 0x7e, 0xff, 0x59, 0x10, 0x30, 0xcc, 0x39, 0xba,
0x0b, 0xb5, 0x63, 0x2f, 0x24, 0x23, 0x7d, 0xb0, 0x96, 0x6c, 0xde, 0xd9, 0xef, 0xff, 0x4c, 0x71,
0x5d, 0x33, 0x2a, 0x93, 0x99, 0xa7, 0xa7, 0x98, 0x30, 0x5a, 0x52, 0xae, 0x7f, 0xe8, 0xf1, 0x77,
0xe6, 0xca, 0x56, 0xdf, 0xce, 0x9f, 0x4b, 0xd0, 0xd8, 0x8f, 0x04, 0x66, 0xc7, 0x9e, 0xaf, 0x1e,
0x63, 0xba, 0x39, 0x60, 0x82, 0x64, 0x28, 0x39, 0x53, 0x85, 0x4e, 0x2b, 0x54, 0xdf, 0x32, 0xef,
0xa4, 0xe0, 0xd2, 0x38, 0x2d, 0x5b, 0x50, 0x66, 0xc0, 0xcd, 0xcb, 0xc8, 0x48, 0x87, 0x22, 0x31,
0xf5, 0x81, 0xfc, 0x94, 0x06, 0x4f, 0xce, 0xa4, 0x80, 0x89, 0x8a, 0xa1, 0x54, 0xd5, 0xed, 0x13,
0x35, 0x50, 0xd3, 0x4e, 0x18, 0xd2, 0x79, 0x02, 0x90, 0xe2, 0xe5, 0xb2, 0x76, 0xcb, 0x28, 0x53,
0x3e, 0x58, 0x0c, 0x96, 0xef, 0xe6, 0x44, 0x9c, 0x6f, 0x60, 0xc1, 0xa5, 0x89, 0xd0, 0x87, 0x01,
0x9b, 0x77, 0x5d, 0xc3, 0x55, 0xdf, 0xd2, 0xea, 0xd0, 0x13, 0xf8, 0xcc, 0x1b, 0xdb, 0xd0, 0x19,
0x32, 0x17, 0x98, 0x4a, 0x21, 0x30, 0xf2, 0xf5, 0xaa, 0x1e, 0x67, 0xca, 0xa9, 0x86, 0x6b, 0x28,
0x79, 0x09, 0x71, 0x9f, 0xc6, 0x58, 0xb9, 0xd5, 0x76, 0x35, 0xe1, 0xdc, 0x83, 0x9a, 0x32, 0x2e,
0xb7, 0x8d, 0xf9, 0x32, 0x98, 0x9b, 0x1a, 0xb3, 0xe2, 0xb9, 0x66, 0xc8, 0xd9, 0xb3, 0xef, 0xcb,
0xcc, 0x15, 0xb3, 0x9d, 0xef, 0x41, 0x83, 0x58, 0x9e, 0x49, 0x82, 0x53, 0x5e, 0x67, 0x12, 0xce,
0x0b, 0x58, 0x7d, 0x16, 0x04, 0xdf, 0x55, 0xcb, 0x9e, 0x6d, 0xc2, 0x7c, 0x57, 0x45, 0x8f, 0x61,
0x55, 0xfb, 0xa5, 0xfd, 0xb4, 0x5a, 0xbe, 0x0f, 0x35, 0x66, 0x63, 0x52, 0xca, 0xba, 0x56, 0x46,
0xc8, 0x8c, 0xc9, 0xc3, 0x22, 0x1f, 0xdf, 0xd9, 0x92, 0xda, 0xc3, 0xb2, 0x0a, 0x1d, 0x39, 0x50,
0xd0, 0xe9, 0xfc, 0x06, 0x56, 0x5f, 0x47, 0x23, 0x12, 0xe1, 0x9d, 0xfe, 0xe1, 0x2b, 0x9c, 0x66,
0x5b, 0x04, 0x55, 0x59, 0x4a, 0x29, 0x43, 0x8b, 0xae, 0xfa, 0x96, 0xe9, 0x27, 0x3a, 0x1a, 0xf8,
0x71, 0xc2, 0x4d, 0x9b, 0xa8, 0x16, 0x1d, 0xed, 0xc4, 0x09, 0x47, 0x37, 0x40, 0x5e, 0xe9, 0x03,
0x1a, 0x8d, 0xc6, 0x6a, 0xf5, 0x17, 0xdd, 0xba, 0x1f, 0x27, 0xaf, 0xa3, 0xd1, 0xd8, 0xf9, 0x91,
0x7a, 0x18, 0x63, 0x1c, 0xb8, 0x5e, 0x14, 0xd0, 0xf0, 0x05, 0x3e, 0xcd, 0x59, 0x48, 0x1f, 0x61,
0x36, 0xd7, 0x7e, 0x5b, 0x82, 0xba, 0xc9, 0x20, 0x6a, 0x43, 0x31, 0x72, 0x8a, 0x59, 0x7a, 0xd2,
0x14, 0x25, 0xdf, 0x89, 0xfa, 0x6b, 0x40, 0x63, 0x41, 0x68, 0x9a, 0x97, 0xda, 0x9a, 0xfb, 0x5a,
0x33, 0x73, 0xfb, 0xae, 0x52, 0xd8, 0x77, 0xeb, 0x50, 0x3b, 0xe6, 0x62, 0x1c, 0xa7, 0xfb, 0x51,
0x53, 0x72, 0x67, 0x5b, 0x7d, 0x0b, 0x4a, 0x9f, 0x25, 0xe5, 0x8b, 0x3c, 0xa4, 0x49, 0x24, 0x06,
0x31, 0x25, 0x91, 0x30, 0xa7, 0x0d, 0x14, 0xab, 0x2f, 0x39, 0xce, 0xef, 0x4b, 0x50, 0xd3, 0x9d,
0x43, 0x59, 0xf1, 0xa7, 0xa9, 0xbb, 0x4c, 0xd4, 0x35, 0xa8, 0x6c, 0x99, 0xb4, 0xa0, 0x2c, 0x5d,
0x87, 0xfa, 0x69, 0x38, 0x88, 0x3d, 0x71, 0x62, 0xa1, 0x9d, 0x86, 0x7d, 0x4f, 0x9c, 0x48, 0xcf,
0xb2, 0x1b, 0x40, 0x8d, 0x6b, 0x88, 0xed, 0x94, 0xab, 0xc4, 0xe6, 0x22, 0x75, 0x7e, 0x25, 0x1f,
0x3a, 0x69, 0xd7, 0x6c, 0x05, 0x2a, 0x49, 0x0a, 0x46, 0x7e, 0x4a, 0xce, 0x30, 0xbd, 0x3b, 0xe4,
0x27, 0xba, 0x0b, 0x4b, 0x5e, 0x10, 0x10, 0x39, 0xdd, 0x1b, 0xed, 0x91, 0xc0, 0xb6, 0x7e, 0x26,
0xb8, 0x9f, 0xf4, 0x60, 0xd1, 0xa6, 0x51, 0x54, 0x83, 0xf2, 0xe9, 0xc3, 0x95, 0xff, 0x53, 0xff,
0x3f, 0x5e, 0x29, 0x6d, 0xff, 0xb3, 0x0d, 0xad, 0x67, 0x43, 0x1c, 0x09, 0x53, 0x96, 0xa3, 0x3d,
0x58, 0x9e, 0x68, 0xf3, 0x22, 0xf3, 0x4e, 0x9b, 0xdd, 0xfd, 0xed, 0xad, 0x6f, 0xe9, 0xb6, 0xf1,
0x96, 0x6d, 0x1b, 0x6f, 0xed, 0x86, 0xb1, 0x18, 0xa3, 0x5d, 0x58, 0x2a, 0x36, 0x44, 0xd1, 0x4d,
0x7b, 0xcb, 0xcc, 0x68, 0x93, 0xce, 0x55, 0xb3, 0x07, 0xcb, 0x13, 0xbd, 0x51, 0x8b, 0x67, 0x76,
0xcb, 0x74, 0xae, 0xa2, 0xa7, 0xd0, 0xcc, 0x35, 0x43, 0x51, 0x57, 0x2b, 0x99, 0xee, 0x8f, 0xce,
0x55, 0xb0, 0x03, 0xed, 0x42, 0x7f, 0x12, 0xf5, 0x8c, 0x3f, 0x33, 0x9a, 0x96, 0x73, 0x95, 0x3c,
0x87, 0x66, 0xae, 0x4d, 0x68, 0x51, 0x4c, 0xf7, 0x22, 0x7b, 0x37, 0x66, 0x8c, 0x98, 0x42, 0xe7,
0x25, 0xb4, 0x0b, 0x4d, 0x3d, 0x0b, 0x64, 0x56, 0x43, 0xb1, 0x77, 0x73, 0xe6, 0x98, 0xd1, 0xb4,
0x07, 0xcb, 0x13, 0x2d, 0x3e, 0x1b, 0xdc, 0xd9, 0x9d, 0xbf, 0xb9, 0x6e, 0x7d, 0xa1, 0x16, 0x3b,
0x57, 0xd3, 0xe6, 0x16, 0x7b, 0xba, 0xa1, 0xd7, 0xbb, 0x35, 0x7b, 0xd0, 0xa0, 0xda, 0x85, 0xa5,
0x62, 0x2f, 0xcf, 0x2a, 0x9b, 0xd9, 0xe1, 0xbb, 0x78, 0xe7, 0x14, 0xda, 0x7a, 0xd9, 0xce, 0x99,
0xd5, 0xed, 0x9b, 0xab, 0xe8, 0x19, 0x80, 0x29, 0x7d, 0x03, 0x12, 0xa5, 0x4b, 0x36, 0x55, 0x72,
0xa7, 0x4b, 0x36, 0xa3, 0x4c, 0x7e, 0x0a, 0xa0, 0x2b, 0xd6, 0x80, 0x26, 0x02, 0x5d, 0xb7, 0x30,
0x26, 0xca, 0xe4, 0x5e, 0x77, 0x7a, 0x60, 0x4a, 0x01, 0x66, 0xec, 0x32, 0x0a, 0x9e, 0x00, 0x64,
0x95, 0xb0, 0x55, 0x30, 0x55, 0x1b, 0x5f, 0x10, 0x83, 0x56, 0xbe, 0xee, 0x45, 0xc6, 0xd7, 0x19,
0xb5, 0xf0, 0x5c, 0x15, 0x8f, 0xa0, 0x95, 0xbf, 0xa6, 0xad, 0x8a, 0x19, 0x57, 0x77, 0x6f, 0xf2,
0x7a, 0x45, 0x3f, 0xb5, 0x1b, 0x35, 0x63, 0x15, 0x36, 0xea, 0x7f, 0xa4, 0x61, 0xe2, 0x7a, 0x2f,
0xe6, 0x91, 0x0f, 0x6b, 0xf8, 0x09, 0xb4, 0xf2, 0xf7, 0xba, 0xc5, 0x3f, 0xe3, 0xae, 0xef, 0x15,
0xee, 0x76, 0xf4, 0x14, 0x96, 0x8a, 0x77, 0x3a, 0xca, 0x1d, 0xca, 0xa9, 0x9b, 0xbe, 0xb7, 0x32,
0x61, 0x98, 0xa3, 0x07, 0x00, 0xd9, 0xdd, 0x6f, 0xd7, 0x6e, 0xaa, 0x1a, 0x98, 0xb0, 0xba, 0x03,
0xed, 0xc2, 0x5b, 0xc1, 0x66, 0x89, 0x59, 0x0f, 0x88, 0x8b, 0x92, 0x78, 0xb1, 0x76, 0xb7, 0xd0,
0x67, 0x56, 0xf4, 0x17, 0xed, 0x9e, 0x7c, 0x9d, 0x62, 0x43, 0x37, 0xa3, 0x76, 0xf9, 0xc0, 0x69,
0xce, 0xd7, 0x22, 0xb9, 0xd3, 0x3c, 0xa3, 0x44, 0x99, 0xa7, 0xe8, 0x79, 0xeb, 0xaf, 0xef, 0x6f,
0x97, 0xfe, 0xf6, 0xfe, 0x76, 0xe9, 0x1f, 0xef, 0x6f, 0x97, 0x8e, 0x6a, 0x6a, 0xf4, 0xc1, 0xbf,
0x03, 0x00, 0x00, 0xff, 0xff, 0x9d, 0xb6, 0xaf, 0x46, 0x1a, 0x1d, 0x00, 0x00,
}

96
virtcontainers/netmon.go Normal file
View File

@ -0,0 +1,96 @@
// Copyright (c) 2018 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
package virtcontainers
import (
"fmt"
"os/exec"
"syscall"
"github.com/sirupsen/logrus"
)
// NetmonConfig is the structure providing specific configuration
// for the network monitor.
type NetmonConfig struct {
Path string
Debug bool
Enable bool
}
// netmonParams is the structure providing specific parameters needed
// for the execution of the network monitor binary.
type netmonParams struct {
netmonPath string
debug bool
logLevel string
runtime string
sandboxID string
}
func netmonLogger() *logrus.Entry {
return virtLog.WithField("subsystem", "netmon")
}
func prepareNetMonParams(params netmonParams) ([]string, error) {
if params.netmonPath == "" {
return []string{}, fmt.Errorf("Netmon path is empty")
}
if params.runtime == "" {
return []string{}, fmt.Errorf("Netmon runtime path is empty")
}
if params.sandboxID == "" {
return []string{}, fmt.Errorf("Netmon sandbox ID is empty")
}
args := []string{params.netmonPath,
"-r", params.runtime,
"-s", params.sandboxID,
}
if params.debug {
args = append(args, "-d")
}
if params.logLevel != "" {
args = append(args, []string{"-log", params.logLevel}...)
}
return args, nil
}
func startNetmon(params netmonParams) (int, error) {
args, err := prepareNetMonParams(params)
if err != nil {
return -1, err
}
cmd := exec.Command(args[0], args[1:]...)
if err := cmd.Start(); err != nil {
return -1, err
}
return cmd.Process.Pid, nil
}
func stopNetmon(pid int) error {
if pid <= 0 {
return nil
}
sig := syscall.SIGKILL
netmonLogger().WithFields(
logrus.Fields{
"netmon-pid": pid,
"netmon-signal": sig,
}).Info("Stopping netmon")
if err := syscall.Kill(pid, sig); err != nil && err != syscall.ESRCH {
return err
}
return nil
}

View File

@ -0,0 +1,61 @@
// Copyright (c) 2018 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
package virtcontainers
import (
"reflect"
"testing"
"github.com/stretchr/testify/assert"
)
const (
testNetmonPath = "/foo/bar/netmon"
testRuntimePath = "/foo/bar/runtime"
)
func TestNetmonLogger(t *testing.T) {
got := netmonLogger()
expected := virtLog.WithField("subsystem", "netmon")
assert.True(t, reflect.DeepEqual(expected, got),
"Got %+v\nExpected %+v", got, expected)
}
func TestPrepareNetMonParams(t *testing.T) {
// Empty netmon path
params := netmonParams{}
got, err := prepareNetMonParams(params)
assert.NotNil(t, err)
assert.Equal(t, got, []string{})
// Empty runtime path
params.netmonPath = testNetmonPath
got, err = prepareNetMonParams(params)
assert.NotNil(t, err)
assert.Equal(t, got, []string{})
// Empty sandbox ID
params.runtime = testRuntimePath
got, err = prepareNetMonParams(params)
assert.NotNil(t, err)
assert.Equal(t, got, []string{})
// Successful case
params.sandboxID = testSandboxID
got, err = prepareNetMonParams(params)
assert.Nil(t, err)
expected := []string{testNetmonPath,
"-r", testRuntimePath,
"-s", testSandboxID}
assert.True(t, reflect.DeepEqual(expected, got),
"Got %+v\nExpected %+v", got, expected)
}
func TestStopNetmon(t *testing.T) {
pid := -1
err := stopNetmon(pid)
assert.Nil(t, err)
}

View File

@ -142,6 +142,7 @@ type NetworkInterfacePair struct {
type NetworkConfig struct {
NetNSPath string
NetNsCreated bool
NetmonConfig NetmonConfig
InterworkingModel NetInterworkingModel
}
@ -257,7 +258,7 @@ func (endpoint *VirtualEndpoint) HotAttach(h hypervisor) error {
return err
}
if _, err := h.hotplugAddDevice(*endpoint, netDev); err != nil {
if _, err := h.hotplugAddDevice(endpoint, netDev); err != nil {
networkLogger().WithError(err).Error("Error attach virtual ep")
return err
}
@ -273,11 +274,10 @@ func (endpoint *VirtualEndpoint) HotDetach(h hypervisor, netNsCreated bool, netN
if err := doNetNS(netNsPath, func(_ ns.NetNS) error {
return xconnectVMNetwork(&(endpoint.NetPair), false, 0, h.hypervisorConfig().DisableVhostNet)
}); err != nil {
networkLogger().WithError(err).Error("Error abridging virtual ep")
return err
networkLogger().WithError(err).Warn("Error un-bridging virtual ep")
}
if _, err := h.hotplugRemoveDevice(*endpoint, netDev); err != nil {
if _, err := h.hotplugRemoveDevice(endpoint, netDev); err != nil {
networkLogger().WithError(err).Error("Error detach virtual ep")
return err
}
@ -475,6 +475,7 @@ type NetworkNamespace struct {
NetNsPath string
NetNsCreated bool
Endpoints []Endpoint
NetmonPID int
}
// TypedJSONEndpoint is used as an intermediate representation for
@ -1151,13 +1152,13 @@ func createVirtualNetworkEndpoint(idx int, ifName string, interworkingModel NetI
// at the time of hypervisor attach and not here
NetPair: NetworkInterfacePair{
ID: uniqueID,
Name: fmt.Sprintf("br%d", idx),
Name: fmt.Sprintf("br%d_kata", idx),
VirtIface: NetworkInterface{
Name: fmt.Sprintf("eth%d", idx),
HardAddr: hardAddr.String(),
},
TAPIface: NetworkInterface{
Name: fmt.Sprintf("tap%d", idx),
Name: fmt.Sprintf("tap%d_kata", idx),
},
NetInterworkingModel: interworkingModel,
},

View File

@ -209,13 +209,13 @@ func TestCreateVirtualNetworkEndpoint(t *testing.T) {
expected := &VirtualEndpoint{
NetPair: NetworkInterfacePair{
ID: "uniqueTestID-4",
Name: "br4",
Name: "br4_kata",
VirtIface: NetworkInterface{
Name: "eth4",
HardAddr: macAddr.String(),
},
TAPIface: NetworkInterface{
Name: "tap4",
Name: "tap4_kata",
},
NetInterworkingModel: DefaultNetInterworkingModel,
},
@ -241,13 +241,13 @@ func TestCreateVirtualNetworkEndpointChooseIfaceName(t *testing.T) {
expected := &VirtualEndpoint{
NetPair: NetworkInterfacePair{
ID: "uniqueTestID-4",
Name: "br4",
Name: "br4_kata",
VirtIface: NetworkInterface{
Name: "eth1",
HardAddr: macAddr.String(),
},
TAPIface: NetworkInterface{
Name: "tap4",
Name: "tap4_kata",
},
NetInterworkingModel: DefaultNetInterworkingModel,
},

View File

@ -103,6 +103,8 @@ type RuntimeConfig struct {
HypervisorType vc.HypervisorType
HypervisorConfig vc.HypervisorConfig
NetmonConfig vc.NetmonConfig
AgentType vc.AgentType
AgentConfig interface{}
@ -325,6 +327,12 @@ func networkConfig(ocispec CompatOCISpec, config RuntimeConfig) (vc.NetworkConfi
}
netConf.InterworkingModel = config.InterNetworkModel
netConf.NetmonConfig = vc.NetmonConfig{
Path: config.NetmonConfig.Path,
Debug: config.NetmonConfig.Debug,
Enable: config.NetmonConfig.Enable,
}
return netConf, nil
}

View File

@ -821,7 +821,7 @@ func (q *qemu) hotplugVFIODevice(device *config.VFIODev, op operation) error {
return nil
}
func (q *qemu) hotplugMacvtap(drive VirtualEndpoint) error {
func (q *qemu) hotplugMacvtap(drive *VirtualEndpoint) error {
var (
VMFdNames []string
VhostFdNames []string
@ -845,7 +845,7 @@ func (q *qemu) hotplugMacvtap(drive VirtualEndpoint) error {
return q.qmpMonitorCh.qmp.ExecuteNetdevAddByFds(q.qmpMonitorCh.ctx, "tap", drive.NetPair.Name, VMFdNames, VhostFdNames)
}
func (q *qemu) hotplugNetDevice(drive VirtualEndpoint, op operation) error {
func (q *qemu) hotplugNetDevice(drive *VirtualEndpoint, op operation) error {
err := q.qmpSetup()
if err != nil {
return err
@ -902,7 +902,7 @@ func (q *qemu) hotplugDevice(devInfo interface{}, devType deviceType, op operati
memdev := devInfo.(*memoryDevice)
return nil, q.hotplugMemory(memdev, op)
case netDev:
device := devInfo.(VirtualEndpoint)
device := devInfo.(*VirtualEndpoint)
return nil, q.hotplugNetDevice(device, op)
default:
return nil, fmt.Errorf("cannot hotplug device: unsupported device type '%v'", devType)

View File

@ -961,6 +961,40 @@ func (s *Sandbox) Delete() error {
return s.storage.deleteSandboxResources(s.id, nil)
}
func (s *Sandbox) startNetworkMonitor() error {
span, _ := s.trace("startNetworkMonitor")
defer span.Finish()
binPath, err := os.Executable()
if err != nil {
return err
}
logLevel := "info"
if s.config.NetworkConfig.NetmonConfig.Debug {
logLevel = "debug"
}
params := netmonParams{
netmonPath: s.config.NetworkConfig.NetmonConfig.Path,
debug: s.config.NetworkConfig.NetmonConfig.Debug,
logLevel: logLevel,
runtime: binPath,
sandboxID: s.id,
}
return s.network.run(s.networkNS.NetNsPath, func() error {
pid, err := startNetmon(params)
if err != nil {
return err
}
s.networkNS.NetmonPID = pid
return nil
})
}
func (s *Sandbox) createNetwork() error {
span, _ := s.trace("createNetwork")
defer span.Finish()
@ -983,6 +1017,12 @@ func (s *Sandbox) createNetwork() error {
}
}
if s.config.NetworkConfig.NetmonConfig.Enable {
if err := s.startNetworkMonitor(); err != nil {
return err
}
}
// Store the network
return s.storage.storeSandboxNetwork(s.id, s.networkNS)
}
@ -991,6 +1031,12 @@ func (s *Sandbox) removeNetwork() error {
span, _ := s.trace("removeNetwork")
defer span.Finish()
if s.config.NetworkConfig.NetmonConfig.Enable {
if err := stopNetmon(s.networkNS.NetmonPID); err != nil {
return err
}
}
// In case there is a factory, the network has been handled through
// some API calls to hotplug some interfaces and routes. This means
// the removal of the network should follow the same logic.
@ -1056,6 +1102,7 @@ func (s *Sandbox) AddInterface(inf *grpc.Interface) (*grpc.Interface, error) {
}
// Add network for vm
inf.PciAddr = endpoint.PCIAddr
return s.agent.updateInterface(inf)
}

View File

@ -11,6 +11,7 @@ import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"reflect"
"sync"
@ -23,6 +24,7 @@ import (
"github.com/kata-containers/runtime/virtcontainers/device/drivers"
"github.com/kata-containers/runtime/virtcontainers/device/manager"
"github.com/kata-containers/runtime/virtcontainers/pkg/annotations"
"golang.org/x/sys/unix"
)
func newHypervisorConfig(kernelParams []Param, hParams []Param) HypervisorConfig {
@ -1722,3 +1724,27 @@ func TestGetNetNs(t *testing.T) {
netNs = s.GetNetNs()
assert.Equal(t, netNs, expected)
}
func TestStartNetworkMonitor(t *testing.T) {
trueBinPath, err := exec.LookPath("true")
assert.Nil(t, err)
assert.NotEmpty(t, trueBinPath)
s := &Sandbox{
id: testSandboxID,
config: &SandboxConfig{
NetworkConfig: NetworkConfig{
NetmonConfig: NetmonConfig{
Path: trueBinPath,
},
},
},
network: &defNetwork{},
networkNS: NetworkNamespace{
NetNsPath: fmt.Sprintf("/proc/%d/task/%d/ns/net", os.Getpid(), unix.Gettid()),
},
}
err = s.startNetworkMonitor()
assert.Nil(t, err)
}