diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 184be738..0c086600 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -281,3 +281,18 @@ jobs: docker exec test docker info docker exec test ./integration-docker.sh docker rm -f test + + build-tags: + name: "Build with disabled drivers tags" + runs-on: ubuntu-24.04 + steps: + - name: "Check out" + uses: actions/checkout@v5 + - name: "Set up Go" + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + - name: "Build with -tags no_gvisortapvsock" + run: go build -v -tags no_gvisortapvsock ./cmd/rootlesskit + - name: "Build with -tags 'no_slirp4netns no_lxcusernic no_gvisortapvsock'" + run: go build -v -tags "no_slirp4netns no_lxcusernic no_gvisortapvsock" ./cmd/rootlesskit diff --git a/cmd/rootlesskit/main.go b/cmd/rootlesskit/main.go index eb91d78c..403a3465 100644 --- a/cmd/rootlesskit/main.go +++ b/cmd/rootlesskit/main.go @@ -83,6 +83,33 @@ Examples: Note: RootlessKit requires /etc/subuid and /etc/subgid to be configured by the real root user. See https://rootlesscontaine.rs/getting-started/common/ . ` + // Build the list of available network drivers for help text + // Only compiled-in drivers will be shown here, so omitted drivers don't appear in --help. + drivers := []string{"host", "none", "pasta(experimental)"} + if slirp4netns.Available { + drivers = append(drivers, "slirp4netns") + } + if vpnkit.Available { + drivers = append(drivers, "vpnkit") + } + if lxcusernic.Available { + drivers = append(drivers, "lxc-user-nic(experimental)") + } + if gvisortapvsock.Available { + drivers = append(drivers, "gvisor-tap-vsock(experimental)") + } + netDriversHelp := strings.Join(drivers, ", ") + + // Build the list of available port drivers for help text + portDrivers := []string{"none", "implicit (for pasta)", "builtin"} + if slirp4netns_port.Available { + portDrivers = append(portDrivers, "slirp4netns") + } + if gvisortapvsock_port.Available { + portDrivers = append(portDrivers, "gvisor-tap-vsock(experimental)") + } + portDriversHelp := strings.Join(portDrivers, ", ") + app.Flags = []cli.Flag{ Categorize(&cli.BoolFlag{ Name: "debug", @@ -99,7 +126,7 @@ See https://rootlesscontaine.rs/getting-started/common/ . }, CategoryState), Categorize(&cli.StringFlag{ Name: "net", - Usage: "network driver [host, none, pasta(experimental), slirp4netns, vpnkit, lxc-user-nic(experimental), gvisor-tap-vsock(experimental)]", + Usage: fmt.Sprintf("network driver [%s]", netDriversHelp), Value: "host", }, CategoryNetwork), Categorize(&cli.StringFlag{ @@ -169,7 +196,7 @@ See https://rootlesscontaine.rs/getting-started/common/ . }, CategoryMount), Categorize(&cli.StringFlag{ Name: "port-driver", - Usage: "port driver for non-host network. [none, implicit (for pasta), builtin, slirp4netns, gvisor-tap-vsock]", + Usage: fmt.Sprintf("port driver for non-host network. [%s]", portDriversHelp), Value: "none", }, CategoryPort), Categorize(&cli.StringSliceFlag{ diff --git a/docs/BUILDING.md b/docs/BUILDING.md new file mode 100644 index 00000000..292176c6 --- /dev/null +++ b/docs/BUILDING.md @@ -0,0 +1,20 @@ +# Building RootlessKit + +This document describes build-time options, including Go build tags for omitting certain network and port drivers. + +## Build tags to omit drivers + +To exclude specific drivers at compilation time, use Go build tags: + +- Tag `no_vpnkit`: omits the VPNKit network driver implementation. +- Tag `no_gvisortapvsock`: omits the gvisor-tap-vsock network driver implementation and its port driver. +- Tag `no_slirp4netns`: omits the slirp4netns network driver implementation and its port driver. +- Tag `no_lxcusernic`: omits the lxc-user-nic network driver implementation. + +Example: + +- Build without VPNKit support: + go build -tags no_vpnkit ./cmd/rootlesskit + +Notes: +- If a disabled driver is selected at runtime (e.g., `--net=vpnkit` when built with `-tags no_vpnkit`), RootlessKit returns an error indicating that the driver was disabled at build time. \ No newline at end of file diff --git a/docs/network.md b/docs/network.md index ab8c9d7a..fc1532f1 100644 --- a/docs/network.md +++ b/docs/network.md @@ -267,3 +267,8 @@ The `--detach-netns` flag (since v2.0.0) detaches network namespaces into `$ROOT and executes the child command in the host's network namespace. The child command can enter `$ROOTLESSKIT_STATE_DIR/netns` by itself to create nested network namespaces. + + +## Build tags to omit drivers + +Build-time driver selection is documented in [BUILDING.md](BUILDING.md). diff --git a/docs/port.md b/docs/port.md index bc17e7fb..e48376a1 100644 --- a/docs/port.md +++ b/docs/port.md @@ -59,3 +59,7 @@ To specify IPv6 explicitly, use `tcp6`, e.g., `[::]:8080:80/tcp6`. The `tcp4` and `tcp6` forms were introduced in RootlessKit v0.14.0. The `tcp6` is currently supported only for `builtin` port driver. + +## Build tags to omit port drivers + +Build-time driver selection is documented in [BUILDING.md](BUILDING.md). diff --git a/pkg/network/gvisortapvsock/gvisortapvsock.go b/pkg/network/gvisortapvsock/gvisortapvsock.go index ed6a33b2..4066905d 100644 --- a/pkg/network/gvisortapvsock/gvisortapvsock.go +++ b/pkg/network/gvisortapvsock/gvisortapvsock.go @@ -1,3 +1,6 @@ +//go:build !no_gvisortapvsock +// +build !no_gvisortapvsock + package gvisortapvsock import ( @@ -32,6 +35,8 @@ const ( DriverName = "gvisor-tap-vsock" // Default buffer size for packet reading/writing defaultBufferSize = 65536 + // Available indicates whether this driver is compiled in (used for generating help text) + Available = true ) // NewParentDriver instantiates a new parent driver diff --git a/pkg/network/gvisortapvsock/gvisortapvsock_disabled.go b/pkg/network/gvisortapvsock/gvisortapvsock_disabled.go new file mode 100644 index 00000000..badc6a61 --- /dev/null +++ b/pkg/network/gvisortapvsock/gvisortapvsock_disabled.go @@ -0,0 +1,48 @@ +//go:build no_gvisortapvsock +// +build no_gvisortapvsock + +package gvisortapvsock + +import ( + "context" + "errors" + "io" + "net" + + "github.com/rootless-containers/rootlesskit/v3/pkg/api" + "github.com/rootless-containers/rootlesskit/v3/pkg/messages" + "github.com/rootless-containers/rootlesskit/v3/pkg/network" +) + +// NewParentDriver returns a stub when built with the no_gvisortapvsock tag. +func NewParentDriver(logWriter io.Writer, mtu int, ipnet *net.IPNet, ifname string, disableHostLoopback bool, enableIPv6 bool) (network.ParentDriver, error) { + return &disabledParent{}, errors.New("gvisor-tap-vsock network driver disabled by build tag no_gvisortapvsock") +} + +type disabledParent struct{} + +func (d *disabledParent) Info(ctx context.Context) (*api.NetworkDriverInfo, error) { + return nil, errors.New("gvisor-tap-vsock network driver disabled by build tag no_gvisortapvsock") +} + +func (d *disabledParent) MTU() int { return 0 } + +func (d *disabledParent) ConfigureNetwork(childPID int, stateDir string, detachedNetNSPath string) (*messages.ParentInitNetworkDriverCompleted, func() error, error) { + return nil, func() error { return nil }, errors.New("gvisor-tap-vsock network driver disabled by build tag no_gvisortapvsock") +} + +// NewChildDriver returns a stub when built with the no_gvisortapvsock tag. +func NewChildDriver() network.ChildDriver { return &disabledChild{} } + +type disabledChild struct{} + +func (d *disabledChild) ChildDriverInfo() (*network.ChildDriverInfo, error) { + return &network.ChildDriverInfo{ConfiguresInterface: false}, nil +} + +func (d *disabledChild) ConfigureNetworkChild(netmsg *messages.ParentInitNetworkDriverCompleted, detachedNetNSPath string) (string, error) { + return "", errors.New("gvisor-tap-vsock network driver disabled by build tag no_gvisortapvsock") +} + +// Available indicates whether this driver is compiled in (used for generating help text) +const Available = false diff --git a/pkg/network/lxcusernic/lxcusernic.go b/pkg/network/lxcusernic/lxcusernic.go index aa0bd984..de4509e6 100644 --- a/pkg/network/lxcusernic/lxcusernic.go +++ b/pkg/network/lxcusernic/lxcusernic.go @@ -1,3 +1,6 @@ +//go:build !no_lxcusernic +// +build !no_lxcusernic + package lxcusernic import ( @@ -214,3 +217,6 @@ func dhcpRenewRoutine(c *client4.Client, dev string, initialIP net.IP, lease tim lease = p.IPAddressLeaseTime(lease) } } + +// Available indicates whether this driver is compiled in (used for generating help text) +const Available = true diff --git a/pkg/network/lxcusernic/lxcusernic_disabled.go b/pkg/network/lxcusernic/lxcusernic_disabled.go new file mode 100644 index 00000000..212b1944 --- /dev/null +++ b/pkg/network/lxcusernic/lxcusernic_disabled.go @@ -0,0 +1,46 @@ +//go:build no_lxcusernic +// +build no_lxcusernic + +package lxcusernic + +import ( + "context" + "errors" + + "github.com/rootless-containers/rootlesskit/v3/pkg/api" + "github.com/rootless-containers/rootlesskit/v3/pkg/messages" + "github.com/rootless-containers/rootlesskit/v3/pkg/network" +) + +// NewParentDriver returns a stub when built with the no_lxcusernic tag. +func NewParentDriver(binary string, mtu int, bridge string, ifname string) (network.ParentDriver, error) { + return &disabledParent{}, errors.New("lxc-user-nic network driver disabled by build tag no_lxcusernic") +} + +type disabledParent struct{} + +func (d *disabledParent) Info(ctx context.Context) (*api.NetworkDriverInfo, error) { + return nil, errors.New("lxc-user-nic network driver disabled by build tag no_lxcusernic") +} + +func (d *disabledParent) MTU() int { return 0 } + +func (d *disabledParent) ConfigureNetwork(childPID int, stateDir string, detachedNetNSPath string) (*messages.ParentInitNetworkDriverCompleted, func() error, error) { + return nil, func() error { return nil }, errors.New("lxc-user-nic network driver disabled by build tag no_lxcusernic") +} + +// NewChildDriver returns a stub when built with the no_lxcusernic tag. +func NewChildDriver() network.ChildDriver { return &disabledChild{} } + +type disabledChild struct{} + +func (d *disabledChild) ChildDriverInfo() (*network.ChildDriverInfo, error) { + return &network.ChildDriverInfo{ConfiguresInterface: false}, nil +} + +func (d *disabledChild) ConfigureNetworkChild(netmsg *messages.ParentInitNetworkDriverCompleted, detachedNetNSPath string) (string, error) { + return "", errors.New("lxc-user-nic network driver disabled by build tag no_lxcusernic") +} + +// Available indicates whether this driver is compiled in (used for generating help text) +const Available = false diff --git a/pkg/network/slirp4netns/slirp4netns.go b/pkg/network/slirp4netns/slirp4netns.go index 392417ec..3c41ae98 100644 --- a/pkg/network/slirp4netns/slirp4netns.go +++ b/pkg/network/slirp4netns/slirp4netns.go @@ -1,3 +1,6 @@ +//go:build !no_slirp4netns +// +build !no_slirp4netns + package slirp4netns import ( @@ -353,3 +356,6 @@ func (d *childDriver) ConfigureNetworkChild(netmsg *messages.ParentInitNetworkDr // and they are up to the child. return tap, nil } + +// Available indicates whether this driver is compiled in (used for generating help text) +const Available = true diff --git a/pkg/network/slirp4netns/slirp4netns_disabled.go b/pkg/network/slirp4netns/slirp4netns_disabled.go new file mode 100644 index 00000000..16f5969b --- /dev/null +++ b/pkg/network/slirp4netns/slirp4netns_disabled.go @@ -0,0 +1,75 @@ +//go:build no_slirp4netns +// +build no_slirp4netns + +package slirp4netns + +import ( + "context" + "errors" + "io" + "net" + + "github.com/rootless-containers/rootlesskit/v3/pkg/api" + "github.com/rootless-containers/rootlesskit/v3/pkg/messages" + "github.com/rootless-containers/rootlesskit/v3/pkg/network" +) + +// Features is defined to satisfy references from cmd when the slirp4netns +// network driver is disabled via the no_slirp4netns build tag. +// It mirrors the shape of the real Features struct. +type Features struct { + // SupportsEnableIPv6 --enable-ipv6 (v0.2.0) + SupportsEnableIPv6 bool + // SupportsCIDR --cidr (v0.3.0) + SupportsCIDR bool + // SupportsDisableHostLoopback --disable-host-loopback (v0.3.0) + SupportsDisableHostLoopback bool + // SupportsAPISocket --api-socket (v0.3.0) + SupportsAPISocket bool + // SupportsEnableSandbox --enable-sandbox (v0.4.0) + SupportsEnableSandbox bool + // SupportsEnableSeccomp --enable-seccomp (v0.4.0) + SupportsEnableSeccomp bool + // KernelSupportsEnableSeccomp whether the kernel supports slirp4netns --enable-seccomp + KernelSupportsEnableSeccomp bool +} + +// DetectFeatures is a stub used when the slirp4netns network driver is +// disabled via the no_slirp4netns build tag. It always returns an error so +// callers can gracefully handle the lack of support at runtime. +func DetectFeatures(binary string) (*Features, error) { + return nil, errors.New("slirp4netns network driver disabled by build tag no_slirp4netns") +} + +// NewParentDriver returns a stub when built with the no_slirp4netns tag. +func NewParentDriver(logWriter io.Writer, binary string, mtu int, ipnet *net.IPNet, ifname string, disableHostLoopback bool, apiSocketPath string, enableSandbox bool, enableSeccomp bool, enableIPv6 bool) (network.ParentDriver, error) { + return &disabledParent{}, errors.New("slirp4netns network driver disabled by build tag no_slirp4netns") +} + +type disabledParent struct{} + +func (d *disabledParent) Info(ctx context.Context) (*api.NetworkDriverInfo, error) { + return nil, errors.New("slirp4netns network driver disabled by build tag no_slirp4netns") +} + +func (d *disabledParent) MTU() int { return 0 } + +func (d *disabledParent) ConfigureNetwork(childPID int, stateDir string, detachedNetNSPath string) (*messages.ParentInitNetworkDriverCompleted, func() error, error) { + return nil, func() error { return nil }, errors.New("slirp4netns network driver disabled by build tag no_slirp4netns") +} + +// NewChildDriver returns a stub when built with the no_slirp4netns tag. +func NewChildDriver() network.ChildDriver { return &disabledChild{} } + +type disabledChild struct{} + +func (d *disabledChild) ChildDriverInfo() (*network.ChildDriverInfo, error) { + return &network.ChildDriverInfo{ConfiguresInterface: false}, nil +} + +func (d *disabledChild) ConfigureNetworkChild(netmsg *messages.ParentInitNetworkDriverCompleted, detachedNetNSPath string) (string, error) { + return "", errors.New("slirp4netns network driver disabled by build tag no_slirp4netns") +} + +// Available indicates whether this driver is compiled in (used for generating help text) +const Available = false diff --git a/pkg/network/vpnkit/vpnkit.go b/pkg/network/vpnkit/vpnkit.go index 339c0994..a26da162 100644 --- a/pkg/network/vpnkit/vpnkit.go +++ b/pkg/network/vpnkit/vpnkit.go @@ -1,3 +1,6 @@ +//go:build !no_vpnkit +// +build !no_vpnkit + package vpnkit import ( @@ -288,3 +291,6 @@ func vif2tap(w io.Writer, vif *vmnet.Vif) { } } } + +// Available indicates whether this driver is compiled in (used for generating help text) +const Available = true diff --git a/pkg/network/vpnkit/vpnkit_disabled.go b/pkg/network/vpnkit/vpnkit_disabled.go new file mode 100644 index 00000000..bdc0623e --- /dev/null +++ b/pkg/network/vpnkit/vpnkit_disabled.go @@ -0,0 +1,46 @@ +//go:build no_vpnkit +// +build no_vpnkit + +package vpnkit + +import ( + "context" + "errors" + + "github.com/rootless-containers/rootlesskit/v3/pkg/api" + "github.com/rootless-containers/rootlesskit/v3/pkg/messages" + "github.com/rootless-containers/rootlesskit/v3/pkg/network" +) + +// NewParentDriver returns a stub when built with the no_vpnkit tag. +func NewParentDriver(binary string, mtu int, ifname string, disableHostLoopback bool) network.ParentDriver { + return &disabledParent{} +} + +type disabledParent struct{} + +func (d *disabledParent) Info(ctx context.Context) (*api.NetworkDriverInfo, error) { + return nil, errors.New("vpnkit network driver disabled by build tag no_vpnkit") +} + +func (d *disabledParent) MTU() int { return 0 } + +func (d *disabledParent) ConfigureNetwork(childPID int, stateDir, detachedNetNSPath string) (*messages.ParentInitNetworkDriverCompleted, func() error, error) { + return nil, func() error { return nil }, errors.New("vpnkit network driver disabled by build tag no_vpnkit") +} + +// NewChildDriver returns a stub when built with the no_vpnkit tag. +func NewChildDriver() network.ChildDriver { return &disabledChild{} } + +type disabledChild struct{} + +func (d *disabledChild) ChildDriverInfo() (*network.ChildDriverInfo, error) { + return &network.ChildDriverInfo{ConfiguresInterface: false}, nil +} + +func (d *disabledChild) ConfigureNetworkChild(netmsg *messages.ParentInitNetworkDriverCompleted, detachedNetNSPath string) (string, error) { + return "", errors.New("vpnkit network driver disabled by build tag no_vpnkit") +} + +// Available indicates whether this driver is compiled in (used for generating help text) +const Available = false diff --git a/pkg/port/builtin/builtin.go b/pkg/port/builtin/builtin.go index fbdc2b40..219f04ca 100644 --- a/pkg/port/builtin/builtin.go +++ b/pkg/port/builtin/builtin.go @@ -12,3 +12,6 @@ var ( NewParentDriver func(logWriter io.Writer, stateDir string) (port.ParentDriver, error) = parent.NewDriver NewChildDriver func(logWriter io.Writer) port.ChildDriver = child.NewDriver ) + +// Available indicates whether this port driver is compiled in (used for generating help text) +const Available = true diff --git a/pkg/port/gvisortapvsock/gvisortapvsock.go b/pkg/port/gvisortapvsock/gvisortapvsock.go index 0c13a43f..e117eb77 100644 --- a/pkg/port/gvisortapvsock/gvisortapvsock.go +++ b/pkg/port/gvisortapvsock/gvisortapvsock.go @@ -1,3 +1,6 @@ +//go:build !no_gvisortapvsock +// +build !no_gvisortapvsock + package gvisortapvsock import ( @@ -341,3 +344,6 @@ func (d *childDriver) RunChildDriver(opaque map[string]string, quit <-chan struc <-quit return nil } + +// Available indicates whether this port driver is compiled in (used for generating help text) +const Available = true diff --git a/pkg/port/gvisortapvsock/gvisortapvsock_disabled.go b/pkg/port/gvisortapvsock/gvisortapvsock_disabled.go new file mode 100644 index 00000000..bac0f556 --- /dev/null +++ b/pkg/port/gvisortapvsock/gvisortapvsock_disabled.go @@ -0,0 +1,54 @@ +//go:build no_gvisortapvsock +// +build no_gvisortapvsock + +package gvisortapvsock + +import ( + "context" + "errors" + "io" + + "github.com/rootless-containers/rootlesskit/v3/pkg/api" + "github.com/rootless-containers/rootlesskit/v3/pkg/port" +) + +// NewParentDriver returns a stub when built with the no_gvisortapvsock tag. +func NewParentDriver(logWriter io.Writer, stateDir string) (port.ParentDriver, error) { + return &disabledParent{}, errors.New("gvisor-tap-vsock port driver disabled by build tag no_gvisortapvsock") +} + +type disabledParent struct{} + +func (d *disabledParent) Info(ctx context.Context) (*api.PortDriverInfo, error) { + return nil, errors.New("gvisor-tap-vsock port driver disabled by build tag no_gvisortapvsock") +} + +func (d *disabledParent) OpaqueForChild() map[string]string { return map[string]string{} } + +func (d *disabledParent) RunParentDriver(initComplete chan struct{}, quit <-chan struct{}, cctx *port.ChildContext) error { + return errors.New("gvisor-tap-vsock port driver disabled by build tag no_gvisortapvsock") +} + +func (d *disabledParent) AddPort(ctx context.Context, spec port.Spec) (*port.Status, error) { + return nil, errors.New("gvisor-tap-vsock port driver disabled by build tag no_gvisortapvsock") +} + +func (d *disabledParent) ListPorts(ctx context.Context) ([]port.Status, error) { + return nil, errors.New("gvisor-tap-vsock port driver disabled by build tag no_gvisortapvsock") +} + +func (d *disabledParent) RemovePort(ctx context.Context, id int) error { + return errors.New("gvisor-tap-vsock port driver disabled by build tag no_gvisortapvsock") +} + +// NewChildDriver returns a stub when built with the no_gvisortapvsock tag. +func NewChildDriver() port.ChildDriver { return &disabledChild{} } + +type disabledChild struct{} + +func (d *disabledChild) RunChildDriver(opaque map[string]string, quit <-chan struct{}, detachedNetNSPath string) error { + return errors.New("gvisor-tap-vsock port driver disabled by build tag no_gvisortapvsock") +} + +// Available indicates whether this port driver is compiled in (used for generating help text) +const Available = false diff --git a/pkg/port/slirp4netns/slirp4netns.go b/pkg/port/slirp4netns/slirp4netns.go index 1fb28b9c..f49cf0be 100644 --- a/pkg/port/slirp4netns/slirp4netns.go +++ b/pkg/port/slirp4netns/slirp4netns.go @@ -1,3 +1,6 @@ +//go:build !no_slirp4netns +// +build !no_slirp4netns + package slirp4netns import ( @@ -206,3 +209,6 @@ func (d *childDriver) RunChildDriver(opaque map[string]string, quit <-chan struc <-quit return nil } + +// Available indicates whether this port driver is compiled in (used for generating help text) +const Available = true diff --git a/pkg/port/slirp4netns/slirp4netns_disabled.go b/pkg/port/slirp4netns/slirp4netns_disabled.go new file mode 100644 index 00000000..a8ee8f8d --- /dev/null +++ b/pkg/port/slirp4netns/slirp4netns_disabled.go @@ -0,0 +1,54 @@ +//go:build no_slirp4netns +// +build no_slirp4netns + +package slirp4netns + +import ( + "context" + "errors" + "io" + + "github.com/rootless-containers/rootlesskit/v3/pkg/api" + "github.com/rootless-containers/rootlesskit/v3/pkg/port" +) + +// NewParentDriver returns a stub when built with the no_slirp4netns tag. +func NewParentDriver(logWriter io.Writer, apiSocketPath string) (port.ParentDriver, error) { + return &disabledParent{}, errors.New("slirp4netns port driver disabled by build tag no_slirp4netns") +} + +type disabledParent struct{} + +func (d *disabledParent) Info(ctx context.Context) (*api.PortDriverInfo, error) { + return nil, errors.New("slirp4netns port driver disabled by build tag no_slirp4netns") +} + +func (d *disabledParent) OpaqueForChild() map[string]string { return map[string]string{} } + +func (d *disabledParent) RunParentDriver(initComplete chan struct{}, quit <-chan struct{}, cctx *port.ChildContext) error { + return errors.New("slirp4netns port driver disabled by build tag no_slirp4netns") +} + +func (d *disabledParent) AddPort(ctx context.Context, spec port.Spec) (*port.Status, error) { + return nil, errors.New("slirp4netns port driver disabled by build tag no_slirp4netns") +} + +func (d *disabledParent) ListPorts(ctx context.Context) ([]port.Status, error) { + return nil, errors.New("slirp4netns port driver disabled by build tag no_slirp4netns") +} + +func (d *disabledParent) RemovePort(ctx context.Context, id int) error { + return errors.New("slirp4netns port driver disabled by build tag no_slirp4netns") +} + +// NewChildDriver returns a stub when built with the no_slirp4netns tag. +func NewChildDriver() port.ChildDriver { return &disabledChild{} } + +type disabledChild struct{} + +func (d *disabledChild) RunChildDriver(opaque map[string]string, quit <-chan struct{}, detachedNetNSPath string) error { + return errors.New("slirp4netns port driver disabled by build tag no_slirp4netns") +} + +// Available indicates whether this port driver is compiled in (used for generating help text) +const Available = false