| 
									
										
										
										
											2022-05-20 19:08:55 +08:00
										 |  |  | //go:build linux || freebsd
 | 
					
						
							| 
									
										
										
										
											2022-01-06 04:36:49 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | package netavark | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"encoding/json" | 
					
						
							|  |  |  | 	"errors" | 
					
						
							|  |  |  | 	"io" | 
					
						
							|  |  |  | 	"os" | 
					
						
							|  |  |  | 	"os/exec" | 
					
						
							|  |  |  | 	"strconv" | 
					
						
							| 
									
										
										
										
											2024-01-26 23:27:18 +08:00
										 |  |  | 	"strings" | 
					
						
							| 
									
										
										
										
											2022-01-06 04:36:49 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	"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 := "" | 
					
						
							| 
									
										
										
										
											2023-09-14 08:46:30 +08:00
										 |  |  | 	// only add the exit code the error message if we have at least info log level
 | 
					
						
							| 
									
										
										
										
											2022-01-06 04:36:49 +08:00
										 |  |  | 	// 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
 | 
					
						
							| 
									
										
										
										
											2025-09-17 03:55:13 +08:00
										 |  |  | // This will write the logrus at info level.
 | 
					
						
							| 
									
										
										
										
											2022-01-06 04:36:49 +08:00
										 |  |  | type logrusNetavarkWriter struct{} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (l *logrusNetavarkWriter) Write(b []byte) (int, error) { | 
					
						
							|  |  |  | 	logrus.Info("netavark: ", string(b)) | 
					
						
							|  |  |  | 	return len(b), nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-17 03:55:13 +08:00
										 |  |  | // getRustLogEnv returns the RUST_LOG env var based on the current logrus level.
 | 
					
						
							| 
									
										
										
										
											2022-01-06 04:36:49 +08:00
										 |  |  | 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.
 | 
					
						
							| 
									
										
										
										
											2024-01-26 23:27:18 +08:00
										 |  |  | func (n *netavarkNetwork) execNetavark(args []string, needPlugin bool, stdin, result any) error { | 
					
						
							| 
									
										
										
										
											2023-04-06 21:00:52 +08:00
										 |  |  | 	// set the netavark log level to the same as the podman
 | 
					
						
							|  |  |  | 	env := append(os.Environ(), getRustLogEnv()) | 
					
						
							| 
									
										
										
										
											2024-01-26 23:27:18 +08:00
										 |  |  | 	// Netavark need access to iptables in $PATH. As it turns out debian doesn't put
 | 
					
						
							|  |  |  | 	// /usr/sbin in $PATH for rootless users. This will break rootless networking completely.
 | 
					
						
							|  |  |  | 	// We might break existing users and we cannot expect everyone to change their $PATH so
 | 
					
						
							|  |  |  | 	// let's add /usr/sbin to $PATH ourselves.
 | 
					
						
							|  |  |  | 	path := os.Getenv("PATH") | 
					
						
							|  |  |  | 	if !strings.Contains(path, "/usr/sbin") { | 
					
						
							|  |  |  | 		path += ":/usr/sbin" | 
					
						
							|  |  |  | 		env = append(env, "PATH="+path) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-04-06 21:00:52 +08:00
										 |  |  | 	// 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) { | 
					
						
							|  |  |  | 		env = append(env, "RUST_BACKTRACE=1") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if n.dnsBindPort != 0 { | 
					
						
							|  |  |  | 		env = append(env, "NETAVARK_DNS_PORT="+strconv.Itoa(int(n.dnsBindPort))) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-12-02 00:57:58 +08:00
										 |  |  | 	if n.firewallDriver != "" { | 
					
						
							|  |  |  | 		env = append(env, "NETAVARK_FW="+n.firewallDriver) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-04-06 21:00:52 +08:00
										 |  |  | 	return n.execBinary(n.netavarkBinary, append(n.getCommonNetavarkOptions(needPlugin), args...), stdin, result, env) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-26 23:27:18 +08:00
										 |  |  | func (n *netavarkNetwork) execPlugin(path string, args []string, stdin, result any) error { | 
					
						
							| 
									
										
										
										
											2023-04-06 21:00:52 +08:00
										 |  |  | 	return n.execBinary(path, args, stdin, result, nil) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-26 23:27:18 +08:00
										 |  |  | func (n *netavarkNetwork) execBinary(path string, args []string, stdin, result any, env []string) error { | 
					
						
							| 
									
										
										
										
											2022-01-06 04:36:49 +08:00
										 |  |  | 	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{}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-06 21:00:52 +08:00
										 |  |  | 	cmd := exec.Command(path, args...) | 
					
						
							| 
									
										
										
										
											2022-01-06 04:36:49 +08:00
										 |  |  | 	// connect the pipes to stdin and stdout
 | 
					
						
							|  |  |  | 	cmd.Stdin = stdinR | 
					
						
							|  |  |  | 	cmd.Stdout = stdoutW | 
					
						
							|  |  |  | 	cmd.Stderr = logWriter | 
					
						
							| 
									
										
										
										
											2023-04-06 21:00:52 +08:00
										 |  |  | 	cmd.Env = env | 
					
						
							| 
									
										
										
										
											2022-01-06 04:36:49 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	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 | 
					
						
							|  |  |  | } |