Signed-off-by: Derek McGowan <derek@mcg.dev>
| ... | ... |
@@ -8,8 +8,8 @@ import ( |
| 8 | 8 |
|
| 9 | 9 |
"github.com/containerd/log" |
| 10 | 10 |
"github.com/docker/docker/daemon/libnetwork/drivers/bridge/internal/firewaller" |
| 11 |
+ "github.com/docker/docker/daemon/libnetwork/internal/modprobe" |
|
| 11 | 12 |
"github.com/docker/docker/daemon/libnetwork/iptables" |
| 12 |
- "github.com/docker/docker/internal/modprobe" |
|
| 13 | 13 |
) |
| 14 | 14 |
|
| 15 | 15 |
const ( |
| ... | ... |
@@ -10,7 +10,7 @@ import ( |
| 10 | 10 |
"syscall" |
| 11 | 11 |
|
| 12 | 12 |
"github.com/containerd/log" |
| 13 |
- "github.com/docker/docker/internal/modprobe" |
|
| 13 |
+ "github.com/docker/docker/daemon/libnetwork/internal/modprobe" |
|
| 14 | 14 |
) |
| 15 | 15 |
|
| 16 | 16 |
// setupIPv4BridgeNetFiltering checks whether IPv4 forwarding is enabled and, if |
| 17 | 17 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,110 @@ |
| 0 |
+// Package modprobe attempts to load kernel modules. It may have more success |
|
| 1 |
+// than simply running "modprobe", particularly for docker-in-docker. |
|
| 2 |
+package modprobe |
|
| 3 |
+ |
|
| 4 |
+import ( |
|
| 5 |
+ "context" |
|
| 6 |
+ "errors" |
|
| 7 |
+ "fmt" |
|
| 8 |
+ "os/exec" |
|
| 9 |
+ "strings" |
|
| 10 |
+ |
|
| 11 |
+ "github.com/containerd/log" |
|
| 12 |
+ "golang.org/x/sys/unix" |
|
| 13 |
+) |
|
| 14 |
+ |
|
| 15 |
+// LoadModules attempts to load kernel modules, if necessary. |
|
| 16 |
+// |
|
| 17 |
+// isLoaded must be a function that checks whether the modules are loaded. It may |
|
| 18 |
+// be called multiple times. isLoaded must return an error to indicate that the |
|
| 19 |
+// modules still need to be loaded, otherwise nil. |
|
| 20 |
+// |
|
| 21 |
+// For each method of loading modules, LoadModules will attempt the load for each |
|
| 22 |
+// of modNames, then it will call isLoaded to check the result - moving on to try |
|
| 23 |
+// the next method if needed, and there is one. |
|
| 24 |
+// |
|
| 25 |
+// The returned error is the result of the final call to isLoaded. |
|
| 26 |
+func LoadModules(ctx context.Context, isLoaded func() error, modNames ...string) error {
|
|
| 27 |
+ if isLoaded() == nil {
|
|
| 28 |
+ log.G(ctx).WithFields(log.Fields{
|
|
| 29 |
+ "modules": modNames, |
|
| 30 |
+ }).Debug("Modules already loaded")
|
|
| 31 |
+ return nil |
|
| 32 |
+ } |
|
| 33 |
+ |
|
| 34 |
+ if err := tryLoad(ctx, isLoaded, modNames, ioctlLoader{}); err != nil {
|
|
| 35 |
+ return tryLoad(ctx, isLoaded, modNames, modprobeLoader{})
|
|
| 36 |
+ } |
|
| 37 |
+ return nil |
|
| 38 |
+} |
|
| 39 |
+ |
|
| 40 |
+type loader interface {
|
|
| 41 |
+ name() string |
|
| 42 |
+ load(modName string) error |
|
| 43 |
+} |
|
| 44 |
+ |
|
| 45 |
+func tryLoad(ctx context.Context, isLoaded func() error, modNames []string, loader loader) error {
|
|
| 46 |
+ var loadErrs []error |
|
| 47 |
+ for _, modName := range modNames {
|
|
| 48 |
+ if err := loader.load(modName); err != nil {
|
|
| 49 |
+ loadErrs = append(loadErrs, err) |
|
| 50 |
+ } |
|
| 51 |
+ } |
|
| 52 |
+ |
|
| 53 |
+ if checkResult := isLoaded(); checkResult != nil {
|
|
| 54 |
+ log.G(ctx).WithFields(log.Fields{
|
|
| 55 |
+ "loader": loader.name(), |
|
| 56 |
+ "modules": modNames, |
|
| 57 |
+ "loadErrors": errors.Join(loadErrs...), |
|
| 58 |
+ "checkResult": checkResult, |
|
| 59 |
+ }).Debug("Modules not loaded")
|
|
| 60 |
+ return checkResult |
|
| 61 |
+ } |
|
| 62 |
+ |
|
| 63 |
+ log.G(ctx).WithFields(log.Fields{
|
|
| 64 |
+ "loader": loader.name(), |
|
| 65 |
+ "modules": modNames, |
|
| 66 |
+ "loadErrors": errors.Join(loadErrs...), |
|
| 67 |
+ }).Debug("Modules loaded")
|
|
| 68 |
+ return nil |
|
| 69 |
+} |
|
| 70 |
+ |
|
| 71 |
+// ioctlLoader attempts to load the module using an ioctl() to get the interface index |
|
| 72 |
+// of a module - it won't have one, but the kernel may load the module. This tends to |
|
| 73 |
+// work in docker-in-docker, where the inner-docker may not have "modprobe" or access |
|
| 74 |
+// to modules in the host's filesystem. |
|
| 75 |
+type ioctlLoader struct{}
|
|
| 76 |
+ |
|
| 77 |
+func (il ioctlLoader) name() string { return "ioctl" }
|
|
| 78 |
+ |
|
| 79 |
+func (il ioctlLoader) load(modName string) error {
|
|
| 80 |
+ sd, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0) |
|
| 81 |
+ if err != nil {
|
|
| 82 |
+ return fmt.Errorf("creating socket for ioctl load of %s: %w", modName, err)
|
|
| 83 |
+ } |
|
| 84 |
+ defer unix.Close(sd) |
|
| 85 |
+ |
|
| 86 |
+ // This tends to work, if running with CAP_SYS_MODULE, because... |
|
| 87 |
+ // https://github.com/torvalds/linux/blob/6f7da290413ba713f0cdd9ff1a2a9bb129ef4f6c/net/core/dev_ioctl.c#L457 |
|
| 88 |
+ // https://github.com/torvalds/linux/blob/6f7da290413ba713f0cdd9ff1a2a9bb129ef4f6c/net/core/dev_ioctl.c#L371-L372 |
|
| 89 |
+ ifreq, err := unix.NewIfreq(modName) |
|
| 90 |
+ if err != nil {
|
|
| 91 |
+ return fmt.Errorf("creating ifreq for %s: %w", modName, err)
|
|
| 92 |
+ } |
|
| 93 |
+ // An error is returned even if the module load is successful. So, ignore it. |
|
| 94 |
+ _ = unix.IoctlIfreq(sd, unix.SIOCGIFINDEX, ifreq) |
|
| 95 |
+ return nil |
|
| 96 |
+} |
|
| 97 |
+ |
|
| 98 |
+// modprobeLoader attempts to load a kernel module using modprobe. |
|
| 99 |
+type modprobeLoader struct{}
|
|
| 100 |
+ |
|
| 101 |
+func (ml modprobeLoader) name() string { return "modprobe" }
|
|
| 102 |
+ |
|
| 103 |
+func (ml modprobeLoader) load(modName string) error {
|
|
| 104 |
+ out, err := exec.Command("modprobe", "-va", modName).CombinedOutput()
|
|
| 105 |
+ if err != nil {
|
|
| 106 |
+ return fmt.Errorf("modprobe %s failed with message: %q, error: %w", modName, strings.TrimSpace(string(out)), err)
|
|
| 107 |
+ } |
|
| 108 |
+ return nil |
|
| 109 |
+} |
| 14 | 14 |
deleted file mode 100644 |
| ... | ... |
@@ -1,110 +0,0 @@ |
| 1 |
-// Package modprobe attempts to load kernel modules. It may have more success |
|
| 2 |
-// than simply running "modprobe", particularly for docker-in-docker. |
|
| 3 |
-package modprobe |
|
| 4 |
- |
|
| 5 |
-import ( |
|
| 6 |
- "context" |
|
| 7 |
- "errors" |
|
| 8 |
- "fmt" |
|
| 9 |
- "os/exec" |
|
| 10 |
- "strings" |
|
| 11 |
- |
|
| 12 |
- "github.com/containerd/log" |
|
| 13 |
- "golang.org/x/sys/unix" |
|
| 14 |
-) |
|
| 15 |
- |
|
| 16 |
-// LoadModules attempts to load kernel modules, if necessary. |
|
| 17 |
-// |
|
| 18 |
-// isLoaded must be a function that checks whether the modules are loaded. It may |
|
| 19 |
-// be called multiple times. isLoaded must return an error to indicate that the |
|
| 20 |
-// modules still need to be loaded, otherwise nil. |
|
| 21 |
-// |
|
| 22 |
-// For each method of loading modules, LoadModules will attempt the load for each |
|
| 23 |
-// of modNames, then it will call isLoaded to check the result - moving on to try |
|
| 24 |
-// the next method if needed, and there is one. |
|
| 25 |
-// |
|
| 26 |
-// The returned error is the result of the final call to isLoaded. |
|
| 27 |
-func LoadModules(ctx context.Context, isLoaded func() error, modNames ...string) error {
|
|
| 28 |
- if isLoaded() == nil {
|
|
| 29 |
- log.G(ctx).WithFields(log.Fields{
|
|
| 30 |
- "modules": modNames, |
|
| 31 |
- }).Debug("Modules already loaded")
|
|
| 32 |
- return nil |
|
| 33 |
- } |
|
| 34 |
- |
|
| 35 |
- if err := tryLoad(ctx, isLoaded, modNames, ioctlLoader{}); err != nil {
|
|
| 36 |
- return tryLoad(ctx, isLoaded, modNames, modprobeLoader{})
|
|
| 37 |
- } |
|
| 38 |
- return nil |
|
| 39 |
-} |
|
| 40 |
- |
|
| 41 |
-type loader interface {
|
|
| 42 |
- name() string |
|
| 43 |
- load(modName string) error |
|
| 44 |
-} |
|
| 45 |
- |
|
| 46 |
-func tryLoad(ctx context.Context, isLoaded func() error, modNames []string, loader loader) error {
|
|
| 47 |
- var loadErrs []error |
|
| 48 |
- for _, modName := range modNames {
|
|
| 49 |
- if err := loader.load(modName); err != nil {
|
|
| 50 |
- loadErrs = append(loadErrs, err) |
|
| 51 |
- } |
|
| 52 |
- } |
|
| 53 |
- |
|
| 54 |
- if checkResult := isLoaded(); checkResult != nil {
|
|
| 55 |
- log.G(ctx).WithFields(log.Fields{
|
|
| 56 |
- "loader": loader.name(), |
|
| 57 |
- "modules": modNames, |
|
| 58 |
- "loadErrors": errors.Join(loadErrs...), |
|
| 59 |
- "checkResult": checkResult, |
|
| 60 |
- }).Debug("Modules not loaded")
|
|
| 61 |
- return checkResult |
|
| 62 |
- } |
|
| 63 |
- |
|
| 64 |
- log.G(ctx).WithFields(log.Fields{
|
|
| 65 |
- "loader": loader.name(), |
|
| 66 |
- "modules": modNames, |
|
| 67 |
- "loadErrors": errors.Join(loadErrs...), |
|
| 68 |
- }).Debug("Modules loaded")
|
|
| 69 |
- return nil |
|
| 70 |
-} |
|
| 71 |
- |
|
| 72 |
-// ioctlLoader attempts to load the module using an ioctl() to get the interface index |
|
| 73 |
-// of a module - it won't have one, but the kernel may load the module. This tends to |
|
| 74 |
-// work in docker-in-docker, where the inner-docker may not have "modprobe" or access |
|
| 75 |
-// to modules in the host's filesystem. |
|
| 76 |
-type ioctlLoader struct{}
|
|
| 77 |
- |
|
| 78 |
-func (il ioctlLoader) name() string { return "ioctl" }
|
|
| 79 |
- |
|
| 80 |
-func (il ioctlLoader) load(modName string) error {
|
|
| 81 |
- sd, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0) |
|
| 82 |
- if err != nil {
|
|
| 83 |
- return fmt.Errorf("creating socket for ioctl load of %s: %w", modName, err)
|
|
| 84 |
- } |
|
| 85 |
- defer unix.Close(sd) |
|
| 86 |
- |
|
| 87 |
- // This tends to work, if running with CAP_SYS_MODULE, because... |
|
| 88 |
- // https://github.com/torvalds/linux/blob/6f7da290413ba713f0cdd9ff1a2a9bb129ef4f6c/net/core/dev_ioctl.c#L457 |
|
| 89 |
- // https://github.com/torvalds/linux/blob/6f7da290413ba713f0cdd9ff1a2a9bb129ef4f6c/net/core/dev_ioctl.c#L371-L372 |
|
| 90 |
- ifreq, err := unix.NewIfreq(modName) |
|
| 91 |
- if err != nil {
|
|
| 92 |
- return fmt.Errorf("creating ifreq for %s: %w", modName, err)
|
|
| 93 |
- } |
|
| 94 |
- // An error is returned even if the module load is successful. So, ignore it. |
|
| 95 |
- _ = unix.IoctlIfreq(sd, unix.SIOCGIFINDEX, ifreq) |
|
| 96 |
- return nil |
|
| 97 |
-} |
|
| 98 |
- |
|
| 99 |
-// modprobeLoader attempts to load a kernel module using modprobe. |
|
| 100 |
-type modprobeLoader struct{}
|
|
| 101 |
- |
|
| 102 |
-func (ml modprobeLoader) name() string { return "modprobe" }
|
|
| 103 |
- |
|
| 104 |
-func (ml modprobeLoader) load(modName string) error {
|
|
| 105 |
- out, err := exec.Command("modprobe", "-va", modName).CombinedOutput()
|
|
| 106 |
- if err != nil {
|
|
| 107 |
- return fmt.Errorf("modprobe %s failed with message: %q, error: %w", modName, strings.TrimSpace(string(out)), err)
|
|
| 108 |
- } |
|
| 109 |
- return nil |
|
| 110 |
-} |