1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
|
// Copyright 2016 CNI authors
// Copyright 2021 Podman authors
//
// This code has been originally copied from github.com/containernetworking/cni
// but has been changed to better fit the Podman use case.
//
// 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
//
// https://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.
// +build linux
package cni
import (
"bytes"
"context"
"encoding/json"
"fmt"
"os/exec"
"path/filepath"
"github.com/containernetworking/cni/pkg/invoke"
"github.com/containernetworking/cni/pkg/version"
"github.com/containers/podman/v3/pkg/rootless"
)
type cniExec struct {
version.PluginDecoder
}
type cniPluginError struct {
plugin string
Code uint `json:"code"`
Msg string `json:"msg"`
Details string `json:"details,omitempty"`
}
// Error returns a nicely formatted error message for the cni plugin errors.
func (e *cniPluginError) Error() string {
err := fmt.Sprintf("cni plugin %s failed", e.plugin)
if e.Msg != "" {
err = fmt.Sprintf("%s: %s", err, e.Msg)
} else if e.Code > 0 {
err = fmt.Sprintf("%s with error code %d", err, e.Code)
}
if e.Details != "" {
err = fmt.Sprintf("%s: %s", err, e.Details)
}
return err
}
// ExecPlugin execute the cni plugin. Returns the stdout of the plugin or an error.
func (e *cniExec) ExecPlugin(ctx context.Context, pluginPath string, stdinData []byte, environ []string) ([]byte, error) {
stdout := &bytes.Buffer{}
stderr := &bytes.Buffer{}
c := exec.CommandContext(ctx, pluginPath)
c.Env = environ
c.Stdin = bytes.NewBuffer(stdinData)
c.Stdout = stdout
c.Stderr = stderr
// The dnsname plugin tries to use XDG_RUNTIME_DIR to store files.
// podman run will have XDG_RUNTIME_DIR set and thus the cni plugin can use
// it. The problem is that XDG_RUNTIME_DIR is unset for the conmon process
// for rootful users. This causes issues since the cleanup process is spawned
// by conmon and thus not have XDG_RUNTIME_DIR set to same value as podman run.
// Because of it dnsname will not find the config files and cannot correctly cleanup.
// To fix this we should also unset XDG_RUNTIME_DIR for the cni plugins as rootful.
if !rootless.IsRootless() {
c.Env = append(c.Env, "XDG_RUNTIME_DIR=")
}
err := c.Run()
if err != nil {
return nil, annotatePluginError(err, pluginPath, stdout.Bytes(), stderr.Bytes())
}
return stdout.Bytes(), nil
}
// annotatePluginError parses the common cni plugin error json.
func annotatePluginError(err error, plugin string, stdout []byte, stderr []byte) error {
pluginName := filepath.Base(plugin)
emsg := cniPluginError{
plugin: pluginName,
}
if len(stdout) == 0 {
if len(stderr) == 0 {
emsg.Msg = err.Error()
} else {
emsg.Msg = string(stderr)
}
} else if perr := json.Unmarshal(stdout, &emsg); perr != nil {
emsg.Msg = fmt.Sprintf("failed to unmarshal error message %q: %v", string(stdout), perr)
}
return &emsg
}
// FindInPath finds the plugin in the given paths.
func (e *cniExec) FindInPath(plugin string, paths []string) (string, error) {
return invoke.FindInPath(plugin, paths)
}
|