docker 对接代理获得接受4:错误的文件描述符

epggiuax  于 2022-12-18  发布在  Docker
关注(0)|答案(2)|浏览(240)

我正在尝试使用docker-proxy向Docker容器添加端口转发,但遇到以下错误,
以下是详细信息,

  • IP为www.example.com的容器172.17.0.2已经在使用--net=none运行。我们提供自己的网络,不使用docker 0网络。
  • 现在我们想向主机公开容器的一些端口,所以考虑尝试docker-agent。
  • 我们执行以下命令,
$ docker-proxy -container-ip 172.17.0.2 -container-port 8000 -host-ip 0.0.0.0   -host-port 8000 -proto tcp
 and we are getting,
 2017/03/16 10:02:30 Stopping proxy on tcp/[::]:8001 for tcp/172.17.0.2:8001 (accept tcp [::]:8001: accept4: bad file descriptor)

Docker版本:Docker版本17.03.0-ce,构建版本60 ccb 22

k2arahey

k2arahey1#

我不认为有任何其他的方法来做到这一点,但停止容器,删除它,然后再次运行它从一个Docker文件开始或简单地与docker运行添加-p 8000:8000。Docker似乎不允许您修补与docker-proxy直接,您必须使用标准命令。
您也可以通过直接修改iptables,即NAT表中的DOCKER chain和filter中的DOCKER chain,手动将端口暴露给外部访问。

iptables -t nat -A DOCKER ! -i your_bridge0 -p tcp -m tcp --dport 8000 -j DNAT --to-destination 172.17.0.2:8000

以及:

iptables -A DOCKER ! -i your_bridge0 -o your_bridge0 -d 172.17.0.2 -p tcp --m tcp --dport 80 -j ACCEPT

当然,你必须确保这些规则能被遵守,这是一个完全不同的问题,Docker似乎并不太关心谁来管理iptables(ufw,firewalld等)。
即使docker-proxy根本没有运行,这也会起作用。docker-proxy绑定到主机的端口,这意味着您可以控制filter表中INPUT链上的流量(主机本身也是如此)。我仍然不明白为什么Docker是这样构建的,但是默认情况下,如果你公开一个容器(使用-p),然后删除DNAT规则,它仍然可以工作,因为请求将直接命中INPUT链,这令人难以置信,但没关系。

0yycz8jy

0yycz8jy2#

这docker-proxy二进制文件不能被调用dockerd外直接,如果你想调用它外无论如何,尝试看看moby源代码的关键代码在这里
有一个例子

package example

    import (
        "fmt"
        "io"
        "net"
        "os"
        "os/exec"
        "runtime"
        "strconv"
        "syscall"
        "time"

        "github.com/ishidawataru/sctp"
    )

    const userlandProxyCommandName = "docker-proxy"

    func newProxyCommand(proto string, hostIP net.IP, hostPort int, containerIP net.IP, containerPort int, proxyPath string) (userlandProxy, error) {
        path := proxyPath
        if proxyPath == "" {
            cmd, err := exec.LookPath(userlandProxyCommandName)
            if err != nil {
                return nil, err
            }
            path = cmd
        }

        args := []string{
            path,
            "-proto", proto,
            "-host-ip", hostIP.String(),
            "-host-port", strconv.Itoa(hostPort),
            "-container-ip", containerIP.String(),
            "-container-port", strconv.Itoa(containerPort),
        }

        return &proxyCommand{
            cmd: &exec.Cmd{
                Path: path,
                Args: args,
                SysProcAttr: &syscall.SysProcAttr{
                    Pdeathsig: syscall.SIGTERM, // send a sigterm to the proxy if the creating thread in the daemon process dies (https://go.dev/issue/27505)
                },
            },
            wait: make(chan error, 1),
        }, nil
    }

    // proxyCommand wraps an exec.Cmd to run the userland TCP and UDP
    // proxies as separate processes.
    type proxyCommand struct {
        cmd  *exec.Cmd
        wait chan error
    }

    func (p *proxyCommand) Start() error {
        r, w, err := os.Pipe()
        if err != nil {
            return fmt.Errorf("proxy unable to open os.Pipe %s", err)
        }
        defer r.Close()
        p.cmd.ExtraFiles = []*os.File{w}

        // As p.cmd.SysProcAttr.Pdeathsig is set, the signal will be sent to the
        // process when the OS thread on which p.cmd.Start() was executed dies.
        // If the thread is allowed to be released back into the goroutine
        // thread pool, the thread could get terminated at any time if a
        // goroutine gets scheduled onto it which calls runtime.LockOSThread()
        // and exits without a matching number of runtime.UnlockOSThread()
        // calls. Ensure that the thread from which Start() is called stays
        // alive until the proxy or the daemon process exits to prevent the
        // proxy from getting terminated early. See https://go.dev/issue/27505
        // for more details.
        started := make(chan error)
        go func() {
            runtime.LockOSThread()
            defer runtime.UnlockOSThread()
            err := p.cmd.Start()
            started <- err
            if err != nil {
                return
            }
            p.wait <- p.cmd.Wait()
        }()
        if err := <-started; err != nil {
            return err
        }
        w.Close()

        errchan := make(chan error, 1)
        go func() {
            buf := make([]byte, 2)
            r.Read(buf)

            if string(buf) != "0\n" {
                errStr, err := io.ReadAll(r)
                if err != nil {
                    errchan <- fmt.Errorf("Error reading exit status from userland proxy: %v", err)
                    return
                }

                errchan <- fmt.Errorf("Error starting userland proxy: %s", errStr)
                return
            }
            errchan <- nil
        }()

        select {
        case err := <-errchan:
            return err
        case <-time.After(16 * time.Second):
            return fmt.Errorf("Timed out proxy starting the userland proxy")
        }
    }

    func (p *proxyCommand) Stop() error {
        if p.cmd.Process != nil {
            if err := p.cmd.Process.Signal(os.Interrupt); err != nil {
                return err
            }
            return <-p.wait
        }
        return nil
    }

    type userlandProxy interface {
        Start() error
        Stop() error
    }

    // ipVersion refers to IP version - v4 or v6
    type ipVersion string

    const (
        // IPv4 is version 4
        ipv4 ipVersion = "4"
        // IPv4 is version 6
        ipv6 ipVersion = "6"
    )

    // dummyProxy just listen on some port, it is needed to prevent accidental
    // port allocations on bound port, because without userland proxy we using
    // iptables rules and not net.Listen
    type dummyProxy struct {
        listener  io.Closer
        addr      net.Addr
        ipVersion ipVersion
    }

    func NewDummyProxy(proto string, hostIP net.IP, hostPort int) (userlandProxy, error) {
        // detect version of hostIP to bind only to correct version
        version := ipv4
        if hostIP.To4() == nil {
            version = ipv6
        }
        switch proto {
        case "tcp":
            addr := &net.TCPAddr{IP: hostIP, Port: hostPort}
            return &dummyProxy{addr: addr, ipVersion: version}, nil
        case "udp":
            addr := &net.UDPAddr{IP: hostIP, Port: hostPort}
            return &dummyProxy{addr: addr, ipVersion: version}, nil
        case "sctp":
            addr := &sctp.SCTPAddr{IPAddrs: []net.IPAddr{{IP: hostIP}}, Port: hostPort}
            return &dummyProxy{addr: addr, ipVersion: version}, nil
        default:
            return nil, fmt.Errorf("Unknown addr type: %s", proto)
        }
    }

    func (p *dummyProxy) Start() error {
        switch addr := p.addr.(type) {
        case *net.TCPAddr:
            l, err := net.ListenTCP("tcp"+string(p.ipVersion), addr)
            if err != nil {
                return err
            }
            p.listener = l
        case *net.UDPAddr:
            l, err := net.ListenUDP("udp"+string(p.ipVersion), addr)
            if err != nil {
                return err
            }
            p.listener = l
        case *sctp.SCTPAddr:
            l, err := sctp.ListenSCTP("sctp"+string(p.ipVersion), addr)
            if err != nil {
                return err
            }
            p.listener = l
        default:
            return fmt.Errorf("Unknown addr type: %T", p.addr)
        }
        return nil
    }

    func (p *dummyProxy) Stop() error {
        if p.listener != nil {
            return p.listener.Close()
        }
        return nil
    }
    func Proxy(hostip string, hostport int, containerip string, containerport int) error {
        // userlandProxy, err := NewDummyProxy("tcp", net.ParseIP("0.0.0.0"), 8888)
        userlandProxy, err := newProxyCommand("tcp", net.ParseIP(hostip), hostport, net.ParseIP(containerip), containerport, "/usr/bin/docker-proxy")
        if err != nil {
            return err
        }
        cleanup := func() error {
            // need to undo the iptables rules before we return
            userlandProxy.Stop()
            // pm.DeleteForwardingTableEntry(m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort)
            // if err := pm.Allocator.ReleasePort(hostIP, m.proto, allocatedHostPort); err != nil {
            //  return err
            // }

            return nil
        }

        if err := userlandProxy.Start(); err != nil {
            if err := cleanup(); err != nil {
                return fmt.Errorf("Error during port allocation cleanup: %v", err)
            }
            return err
        }
        return nil
    }

并在主包中调用它,如:

func main() {
// ne(proto, hostIP, allocatedHostPort, sctpAddr.IPAddrs[0].IP, sctpAddr.Port, pm.proxyPath)

if err := example.Proxy("0.0.0.0", 8080, "172.17.0.10", 8080); err != nil {
    fmt.Printf("err.Error(): %v\n", err.Error())
}
time.Sleep(time.Second * 500)

}

相关问题