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 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
|
//go:build linux || freebsd
// +build linux freebsd
package netavark
import (
"encoding/json"
"errors"
"io"
"os"
"os/exec"
"strconv"
"github.com/sirupsen/logrus"
)
type netavarkError struct {
exitCode int
// Set the json key to "error" so we can directly unmarshal into this struct
Msg string `json:"error"`
err error
}
func (e *netavarkError) Error() string {
ec := ""
// only add the exit code the the error message if we have at least info log level
// the normal user does not need to care about the number
if e.exitCode > 0 && logrus.IsLevelEnabled(logrus.InfoLevel) {
ec = " (exit code " + strconv.Itoa(e.exitCode) + ")"
}
msg := "netavark" + ec
if len(msg) > 0 {
msg += ": " + e.Msg
}
if e.err != nil {
msg += ": " + e.err.Error()
}
return msg
}
func (e *netavarkError) Unwrap() error {
return e.err
}
func newNetavarkError(msg string, err error) error {
return &netavarkError{
Msg: msg,
err: err,
}
}
// Type to implement io.Writer interface
// This will write the logrus at info level
type logrusNetavarkWriter struct{}
func (l *logrusNetavarkWriter) Write(b []byte) (int, error) {
logrus.Info("netavark: ", string(b))
return len(b), nil
}
// getRustLogEnv returns the RUST_LOG env var based on the current logrus level
func getRustLogEnv() string {
level := logrus.GetLevel().String()
// rust env_log uses warn instead of warning
if level == "warning" {
level = "warn"
}
// the rust netlink library is very verbose
// make sure to only log netavark logs
return "RUST_LOG=netavark=" + level
}
// execNetavark will execute netavark with the following arguments
// It takes the path to the binary, the list of args and an interface which is
// marshaled to json and send via stdin to netavark. The result interface is
// used to marshal the netavark output into it. This can be nil.
// All errors return by this function should be of the type netavarkError
// to provide a helpful error message.
func (n *netavarkNetwork) execNetavark(args []string, stdin, result interface{}) error {
stdinR, stdinW, err := os.Pipe()
if err != nil {
return newNetavarkError("failed to create stdin pipe", err)
}
stdinWClosed := false
defer func() {
stdinR.Close()
if !stdinWClosed {
stdinW.Close()
}
}()
stdoutR, stdoutW, err := os.Pipe()
if err != nil {
return newNetavarkError("failed to create stdout pipe", err)
}
stdoutWClosed := false
defer func() {
stdoutR.Close()
if !stdoutWClosed {
stdoutW.Close()
}
}()
// connect stderr to the podman stderr for logging
var logWriter io.Writer = os.Stderr
if n.syslog {
// connect logrus to stderr as well so that the logs will be written to the syslog as well
logWriter = io.MultiWriter(logWriter, &logrusNetavarkWriter{})
}
cmd := exec.Command(n.netavarkBinary, append(n.getCommonNetavarkOptions(), args...)...)
// connect the pipes to stdin and stdout
cmd.Stdin = stdinR
cmd.Stdout = stdoutW
cmd.Stderr = logWriter
// set the netavark log level to the same as the podman
cmd.Env = append(os.Environ(), getRustLogEnv())
// if we run with debug log level lets also set RUST_BACKTRACE=1 so we can get the full stack trace in case of panics
if logrus.IsLevelEnabled(logrus.DebugLevel) {
cmd.Env = append(cmd.Env, "RUST_BACKTRACE=1")
}
if n.dnsBindPort != 0 {
cmd.Env = append(cmd.Env, "NETAVARK_DNS_PORT="+strconv.Itoa(int(n.dnsBindPort)))
}
err = cmd.Start()
if err != nil {
return newNetavarkError("failed to start process", err)
}
err = json.NewEncoder(stdinW).Encode(stdin)
// we have to close stdinW so netavark gets the EOF and does not hang forever
stdinW.Close()
stdinWClosed = true
if err != nil {
return newNetavarkError("failed to encode stdin data", err)
}
dec := json.NewDecoder(stdoutR)
err = cmd.Wait()
// we have to close stdoutW so we can decode the json without hanging forever
stdoutW.Close()
stdoutWClosed = true
if err != nil {
exitError := &exec.ExitError{}
if errors.As(err, &exitError) {
ne := &netavarkError{}
// lets disallow unknown fields to make sure we do not get some unexpected stuff
dec.DisallowUnknownFields()
// this will unmarshal the error message into the error struct
ne.err = dec.Decode(ne)
ne.exitCode = exitError.ExitCode()
return ne
}
return newNetavarkError("unexpected failure during execution", err)
}
if result != nil {
err = dec.Decode(result)
if err != nil {
return newNetavarkError("failed to decode result", err)
}
}
return nil
}
|