From 501643c8bde591fcb8e4c42564f1b854128ad2f7 Mon Sep 17 00:00:00 2001
From: Paul Holzinger <pholzing@redhat.com>
Date: Fri, 19 Nov 2021 18:15:53 +0100
Subject: Make sure netavark output is logged to the syslog

Create a custom writer which logs the netavark output to logrus. This
will log to the syslog when it is enabled.

Signed-off-by: Paul Holzinger <pholzing@redhat.com>
---
 libpod/network/netavark/exec.go    | 45 ++++++++++++++++++++++++++++++++------
 libpod/network/netavark/network.go |  9 ++++++++
 libpod/network/netavark/run.go     |  4 ++--
 libpod/options.go                  |  8 +++++++
 libpod/runtime.go                  |  6 +++++
 pkg/domain/infra/runtime_libpod.go |  5 +++++
 6 files changed, 68 insertions(+), 9 deletions(-)

diff --git a/libpod/network/netavark/exec.go b/libpod/network/netavark/exec.go
index d6458eeb4..01dea8489 100644
--- a/libpod/network/netavark/exec.go
+++ b/libpod/network/netavark/exec.go
@@ -3,6 +3,7 @@ package netavark
 import (
 	"encoding/json"
 	"errors"
+	"io"
 	"os"
 	"os/exec"
 	"strconv"
@@ -45,6 +46,15 @@ func newNetavarkError(msg string, err error) error {
 	}
 }
 
+// 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()
@@ -63,26 +73,43 @@ func getRustLogEnv() string {
 // 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 execNetavark(binary string, args []string, stdin, result interface{}) error {
+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)
 	}
-	defer stdinR.Close()
+	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)
 	}
-	defer stdoutR.Close()
-	defer stdoutW.Close()
+	stdoutWClosed := false
+	defer func() {
+		stdoutR.Close()
+		if !stdoutWClosed {
+			stdoutW.Close()
+		}
+	}()
 
-	cmd := exec.Command(binary, args...)
+	// 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, args...)
 	// connect the pipes to stdin and stdout
 	cmd.Stdin = stdinR
 	cmd.Stdout = stdoutW
-	// connect stderr to the podman stderr for logging
-	cmd.Stderr = os.Stderr
+	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
@@ -95,7 +122,9 @@ func execNetavark(binary string, args []string, stdin, result interface{}) error
 		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)
 	}
@@ -103,7 +132,9 @@ func execNetavark(binary string, args []string, stdin, result interface{}) error
 	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) {
diff --git a/libpod/network/netavark/network.go b/libpod/network/netavark/network.go
index cc6fb423c..540d8d6e5 100644
--- a/libpod/network/netavark/network.go
+++ b/libpod/network/netavark/network.go
@@ -37,6 +37,10 @@ type netavarkNetwork struct {
 	// isMachine describes whenever podman runs in a podman machine environment.
 	isMachine bool
 
+	// syslog describes whenever the netavark debbug output should be log to the syslog as well.
+	// This will use logrus to do so, make sure logrus is set up to log to the syslog.
+	syslog bool
+
 	// lock is a internal lock for critical operations
 	lock lockfile.Locker
 
@@ -68,6 +72,10 @@ type InitConfig struct {
 
 	// LockFile is the path to lock file.
 	LockFile string
+
+	// Syslog describes whenever the netavark debbug output should be log to the syslog as well.
+	// This will use logrus to do so, make sure logrus is set up to log to the syslog.
+	Syslog bool
 }
 
 // NewNetworkInterface creates the ContainerNetwork interface for the netavark backend.
@@ -122,6 +130,7 @@ func NewNetworkInterface(conf InitConfig) (types.ContainerNetwork, error) {
 		defaultSubnet:    defaultNet,
 		isMachine:        conf.IsMachine,
 		lock:             lock,
+		syslog:           conf.Syslog,
 	}
 
 	return n, nil
diff --git a/libpod/network/netavark/run.go b/libpod/network/netavark/run.go
index 2f839151e..54917a981 100644
--- a/libpod/network/netavark/run.go
+++ b/libpod/network/netavark/run.go
@@ -54,7 +54,7 @@ func (n *netavarkNetwork) Setup(namespacePath string, options types.SetupOptions
 	}
 
 	result := map[string]types.StatusBlock{}
-	err = execNetavark(n.netavarkBinary, []string{"setup", namespacePath}, netavarkOpts, &result)
+	err = n.execNetavark([]string{"setup", namespacePath}, netavarkOpts, &result)
 
 	if len(result) != len(options.Networks) {
 		logrus.Errorf("unexpected netavark result: %v", result)
@@ -86,7 +86,7 @@ func (n *netavarkNetwork) Teardown(namespacePath string, options types.TeardownO
 		return errors.Wrap(err, "failed to convert net opts")
 	}
 
-	retErr := execNetavark(n.netavarkBinary, []string{"teardown", namespacePath}, netavarkOpts, nil)
+	retErr := n.execNetavark([]string{"teardown", namespacePath}, netavarkOpts, nil)
 
 	// when netavark returned an error we still free the used ips
 	// otherwise we could end up in a state where block the ips forever
diff --git a/libpod/options.go b/libpod/options.go
index 3f0f9fbe0..8f2d5cb15 100644
--- a/libpod/options.go
+++ b/libpod/options.go
@@ -578,6 +578,14 @@ func WithEnableSDNotify() RuntimeOption {
 	}
 }
 
+// WithSyslog sets a runtime option so we know that we have to log to the syslog as well
+func WithSyslog() RuntimeOption {
+	return func(rt *Runtime) error {
+		rt.syslog = true
+		return nil
+	}
+}
+
 // WithRuntimeFlags adds the global runtime flags to the container config
 func WithRuntimeFlags(runtimeFlags []string) RuntimeOption {
 	return func(rt *Runtime) error {
diff --git a/libpod/runtime.go b/libpod/runtime.go
index c751df79b..1a22cd09a 100644
--- a/libpod/runtime.go
+++ b/libpod/runtime.go
@@ -88,6 +88,11 @@ type Runtime struct {
 	libimageEventsShutdown chan bool
 	lockManager            lock.Manager
 
+	// syslog describes whenever logrus should log to the syslog as well.
+	// Note that the syslog hook will be enabled early in cmd/podman/syslog_linux.go
+	// This bool is just needed so that we can set it for netavark interface.
+	syslog bool
+
 	// doRenumber indicates that the runtime should perform a lock renumber
 	// during initialization.
 	// Once the runtime has been initialized and returned, this variable is
@@ -517,6 +522,7 @@ func makeRuntime(ctx context.Context, runtime *Runtime) (retErr error) {
 			DefaultSubnet:    runtime.config.Network.DefaultSubnet,
 			IsMachine:        runtime.config.Engine.MachineEnabled,
 			LockFile:         filepath.Join(runtime.config.Network.NetworkConfigDir, "netavark.lock"),
+			Syslog:           runtime.syslog,
 		})
 		if err != nil {
 			return errors.Wrapf(err, "could not create network interface")
diff --git a/pkg/domain/infra/runtime_libpod.go b/pkg/domain/infra/runtime_libpod.go
index cfb674b6d..90eb6abeb 100644
--- a/pkg/domain/infra/runtime_libpod.go
+++ b/pkg/domain/infra/runtime_libpod.go
@@ -236,6 +236,11 @@ func getRuntime(ctx context.Context, fs *flag.FlagSet, opts *engineOpts) (*libpo
 		options = append(options, libpod.WithRegistriesConf(cfg.RegistriesConf))
 	}
 
+	// no need to handle the error, it will return false anyway
+	if syslog, _ := fs.GetBool("syslog"); syslog {
+		options = append(options, libpod.WithSyslog())
+	}
+
 	// TODO flag to set CNI plugins dir?
 
 	if !opts.withFDS {
-- 
cgit v1.2.3-54-g00ecf