package buildkit
import (
"context"
"net"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/containerd/log"
"github.com/moby/buildkit/executor"
"github.com/moby/buildkit/executor/resources"
"github.com/moby/buildkit/executor/runcexecutor"
"github.com/moby/buildkit/solver/pb"
"github.com/moby/buildkit/util/network"
"github.com/moby/buildkit/util/network/proxyprovider"
"github.com/moby/moby/v2/daemon/internal/stringid"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
)
const networkName = "bridge"
func newExecutor(opts executorOpts) (executor.Executor, network.ProxyProvider, error) {
netRoot := filepath.Join(opts.root, "net")
networkProviders := map[pb.NetMode]network.Provider{
pb.NetMode_UNSET: &bridgeProvider{Controller: opts.networkController, Root: netRoot},
pb.NetMode_HOST: network.NewHostProvider(),
pb.NetMode_NONE: network.NewNoneProvider(),
}
// make sure net state directory is cleared from previous state
fis, err := os.ReadDir(netRoot)
if err == nil {
for _, fi := range fis {
fp := filepath.Join(netRoot, fi.Name())
if err := os.RemoveAll(fp); err != nil {
log.G(context.TODO()).WithError(err).Errorf("failed to delete old network state: %v", fp)
}
}
}
// Returning a non-nil but empty *IdentityMapping breaks BuildKit:
// https://github.com/moby/moby/pull/39444
idmap := &opts.identityMapping
if opts.identityMapping.Empty() {
idmap = nil
}
rm, err := resources.NewMonitor()
if err != nil {
return nil, nil, err
}
// TODO: FIXME: testing env var, replace with something better or remove in a major version or two
runcCmds := []string{"runc"}
if runcOverride := os.Getenv("DOCKER_BUILDKIT_RUNC_COMMAND"); runcOverride != "" {
runcCmds = []string{runcOverride}
}
proxyProvider := opts.proxyProvider
ownsProxyProvider := false
if proxyProvider == nil && proxyprovider.Supported() {
hostProvider := networkProviders[pb.NetMode_HOST]
egressProviders := map[pb.NetMode]network.Provider{
pb.NetMode_UNSET: loopbackFilteredProvider{provider: hostProvider},
pb.NetMode_HOST: hostProvider,
}
proxyProvider, err = proxyprovider.New(proxyprovider.Opt{
Root: filepath.Join(opts.root, "proxy"),
EgressProviders: egressProviders,
})
if err != nil {
return nil, nil, err
}
ownsProxyProvider = true
}
exec, err := runcexecutor.New(runcexecutor.Opt{
Root: filepath.Join(opts.root, "executor"),
CommandCandidates: runcCmds,
DefaultCgroupParent: opts.cgroupParent,
Rootless: opts.rootless,
NoPivot: os.Getenv("DOCKER_RAMDISK") != "",
IdentityMapping: idmap,
DNS: opts.dnsConfig,
ApparmorProfile: opts.apparmorProfile,
ResourceMonitor: rm,
CDIManager: opts.cdiManager,
ProxyProvider: proxyProvider,
}, networkProviders)
if err != nil {
if ownsProxyProvider {
_ = proxyProvider.Close()
}
return nil, nil, err
}
return exec, proxyProvider, nil
}
// newExecutorGD calls newExecutor() on Linux. It returns a stubExecutor on
// other platforms.
func newExecutorGD(opts executorOpts) (executor.Executor, network.ProxyProvider, error) {
return newExecutor(opts)
}
type loopbackFilteredProvider struct {
provider network.Provider
}
func (p loopbackFilteredProvider) New(ctx context.Context, hostname string, opt network.NamespaceOptions) (network.Namespace, error) {
ns, err := p.provider.New(ctx, hostname, opt)
if err != nil {
return nil, err
}
return loopbackFilteredNS{Namespace: ns}, nil
}
func (p loopbackFilteredProvider) Close() error {
return nil
}
type loopbackFilteredNS struct {
network.Namespace
}
func (n loopbackFilteredNS) DialContext(ctx context.Context, networkName, address string) (net.Conn, error) {
if isLoopbackAddress(ctx, address) {
return nil, errors.Errorf("proxy egress to loopback address %s is not allowed", address)
}
dialer, ok := n.Namespace.(network.Dialer)
if !ok {
return nil, errors.Errorf("proxy egress network does not support dialing")
}
return dialer.DialContext(ctx, networkName, address)
}
func isLoopbackAddress(ctx context.Context, address string) bool {
host, _, err := net.SplitHostPort(address)
if err != nil {
host = address
}
host = strings.Trim(host, "[]")
if strings.EqualFold(host, "localhost") {
return true
}
if ip := net.ParseIP(host); ip != nil {
return ip.IsLoopback()
}
addrs, err := net.DefaultResolver.LookupIPAddr(ctx, host)
if err != nil {
return false
}
for _, addr := range addrs {
if addr.IP.IsLoopback() {
return true
}
}
return false
}
func (iface *lnInterface) Set(s *specs.Spec) error {
<-iface.ready
if iface.err != nil {
log.G(context.TODO()).WithError(iface.err).Error("failed to set networking spec")
return iface.err
}
shortNetCtlrID := stringid.TruncateID(iface.provider.Controller.ID())
// attach netns to bridge within the container namespace, using reexec in a prestart hook
s.Hooks = &specs.Hooks{
Prestart: []specs.Hook{{
Path: filepath.Join("/proc", strconv.Itoa(os.Getpid()), "exe"),
Args: []string{"libnetwork-setkey", "-exec-root=" + iface.provider.Config().ExecRoot, iface.sbx.ContainerID(), shortNetCtlrID},
}},
}
return nil
}