1

This is probably going to sound very stupid so please bear with me.

My goal is to be able to use the Docker API attach functionality like a TCP socket, so I can use it to achieve something like an SSH or kubectl port forwarding.

I have a library Docker.DotNet that provides a way to get the stdio streams from the Docker API, but no functionality like kubectl port-forward. I would like to be able to leverage the ability to attach to stdio to use it like port-forward.

This works for port-forwarding HTTP traffic (adapted from kubectl Windows client):

            var createContainerResponse = await _dockerClient.Containers.CreateContainerAsync(new CreateContainerParameters
            {
                Image = "nginx",
                Cmd = new List<string>() { },
                AttachStdin = false,
                AttachStdout = false,
                AttachStderr = false,
                HostConfig = new HostConfig() {},
                ExposedPorts = new Dictionary<string, EmptyStruct>
                {
                    {
                        "80/tcp",
                        new EmptyStruct()
                    }
                }
            });

            var nginxId = createContainerResponse.ID;

            var started = await _dockerClient.Containers.StartContainerAsync(nginxId, new ContainerStartParameters());

            var inspectResponse = await _dockerClient.Containers.InspectContainerAsync(nginxId);
            var ip = inspectResponse.NetworkSettings.IPAddress;

            var socatContainerResponse = await _dockerClient.Containers.CreateContainerAsync(
                new CreateContainerParameters
                {
                    Image = "alpine/socat",
                    Cmd = new List<string>
                    {
                        "STDIO",
                        $"TCP-CONNECT:{ip}:80"
                    },
                    AttachStdin = true,
                    AttachStderr = true,
                    AttachStdout = true,
                    OpenStdin = true,
                    Tty = false,

                });

            var socatid = socatContainerResponse.ID;

            var stdio = await _dockerClient.Containers.AttachContainerAsync(socatid, false,
                new ContainerAttachParameters() {Stdout = true, Stream = true, Stdin = true, Stderr = true});
            
            await _dockerClient.Containers.StartContainerAsync(socatid, new ContainerStartParameters());


            
            IPAddress ipAddress = IPAddress.Loopback;
            IPEndPoint localEndPoint = new IPEndPoint(ipAddress, 1213);  
            Socket listener = new Socket(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp); 
            listener.Bind(localEndPoint);  
            listener.Listen(100);

            Socket handler = null;

            // Note this will only accept a single connection
            var accept = Task.Run(async () => {
                while (true) {
                    handler = listener.Accept();
                    var bytes = new byte[4096];
                    while (true) {  
                        int bytesRec = handler.Receive(bytes);
                        await stdio.WriteAsync(bytes, 0, bytesRec, default);
                        if (bytesRec == 0 || Encoding.ASCII.GetString(bytes,0,bytesRec).IndexOf("<EOF>") > -1) {  
                            break;  
                        }
                    }
                }
            });

            var copy = Task.Run(async () => {
                var buff = new byte[4096];
                while (true)
                {
                    var read = await stdio.ReadOutputAsync(buff, 0, 4096, default);
                    handler.Send(buff, read.Count, 0);
                }
            });

            await accept;
            await copy;
            if (handler != null) {
                handler.Close();
            }
            listener.Close();

If I run this, it will reliably forward local port 1213 to the container port 80, using socat.

However, if I try to do the same with pyzmq, it does not work. What happens is that nothing ever gets written out to the socket. Also, it appears that listener.Accept is being called over and over again. My client code is very simple:

import sys
import zmq
import time

port = '5556'

# Socket to talk to server
context = zmq.Context()
socket = context.socket(zmq.SUB)

socket.connect ('tcp://myhost:1213')

socket.setsockopt_string(zmq.SUBSCRIBE, 'output')
x = socket.recv_string()
print(x)

It gets stuck at recv_string because nothing is ever written to the socket, even though my producer is definitely constantly producing strings. If I instead replace myhost:1213 with the actual port that I am trying to redirect, it works.

If you can't tell I am a newb... so what is different about HTTP vs ZeroMQ PUB/SUB where the example code works for HTTP forwarding but not (seemingly simpler) ZeroMQ PUB/SUB protocol over TCP?

tacos_tacos_tacos
  • 10,277
  • 11
  • 73
  • 126
  • Well, not seeing the PUB-side processing, hard to tell. I would start with (a) complete the MCVE-definition here, (b) start with SUB-side subscribe to `""`-string (~ everything ) (c) use rather explicit IP-address, avoiding `localhost` symbol translation (d) if needed, adding `zmq_socket_monitor()`-diagnostics on either side of the `PUB / SUB`-relation, so as to see all ISO-OSI-L2-level events and more - finally, not known to me, is the used port-forwarding sure to be transparent port-level forwarding, or is it a protocol-Gateway, capable of HTTP*-forwards, yet not a binary-transparent pFWD? – user3666197 Jan 11 '22 at 06:50

0 Answers0