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?