2

I’m trying to write a request/response TCP server to handle a small number of very simple commands. I'm using a shell script to be as portable as possible, avoiding compilation or requiring specific runtimes of a programming language.

I would like it to be able to respond to the client saying the result of the command, meaning the response has to be dynamic.

I'm currently trying to use nc:

mkfifo commands
mkfifo nc_responses

nc -k -l 47201 >commands < <(tail -f nc_responses) &

while read -r command; do
  echo "Got $command"
  echo "Ran $command" >nc_responses
done <commands

Here's the output of calling it in a second shell:

$ nc localhost 47201 <<< 'command 1'
$ nc localhost 47201 <<< 'command 2'
Ran command 1
$ nc localhost 47201 <<< 'command 3'
Ran command 2
$ nc localhost 47201 <<< 'command 4'
Ran command 3 

As you can see, the response lags the request by one.

Can anyone see a way to fix that, so that the response is dynamically generated from the request?

(For context - this is using bash on macOS, with macOS's built-in nc)

roaima
  • 107,089
  • 14
  • 139
  • 261
  • Does it have to be with `nc`? Do you also have `socat`? That's pretty insecure anyway, doesn't `ssh` work for you? – Eduardo Trápani Dec 22 '20 at 15:57
  • socat isn't installed by default on macOS. ssh... the communication is between a docker container (the client) and this process running on the docker host. sshd felt like opening up a can of worms, I don't want to force the user to run sshd just for this, but perhaps I should revisit it. Security isn't much of an issue, I lock down the set of commands and if something else sent them maliciously... meh, no harm done. – Robert Elliot Dec 22 '20 at 16:15
  • If you install a newer `bash` than the ancient default `bash` on macOS (e.g. from Homebrew), then my answer here will possibly help: https://unix.stackexchange.com/a/336919/116858 – Kusalananda Dec 23 '20 at 10:42
  • @Kusalananda that seems to behave exactly the same way as the named pipes in my solution; it's fine for a long running client, but if you switch to a client that closes after sending its input (as in my output) you get the output from the previous command. Makes me think that perhaps the problem is in the client rather than the server... clearly `nc localhost 47201 <<< 'command 1'` doesn't await the resulting input. – Robert Elliot Dec 26 '20 at 11:05

2 Answers2

0

Riffing on this suggestion elsewhere: https://superuser.com/a/998731

it looks like the problem is in the client rather than the server;

nc localhost 47201 <<< 'command 1'`

doesn't keep the connection open long enough for the response to come through.

This works:

{ echo 'command 1'; sleep 0.1; } | nc localhost 47201

though using sleep feels horrible! I wonder if there's a better way for a client to wait (with timeout?) for a response with a known terminal?

EOF wouldn't easily work because I can't open up a new input pipe per connection..

  • It's possible what I need to do is stop using `-k` and instead run `nc` in a loop, opening a new named response pipe per run so that I can just close it to signal to the client that the response has been sent, but it seems a bit sad when the `-k` option exists. I guess this is the same problem as keepalive with http... – Robert Elliot Dec 26 '20 at 12:02
0

Though it might not be related to the question, here is another way to achieve similar functionality using socat instead of netcat. No need to use named pipeline nor sleep. Please reference socat manual for more information.

server:

> socat TCP-LISTEN:5000,reuseaddr,fork SYSTEM:'read -r command \
echo "Run $command from $SOCAT_PEERADDR" >&2 \
$command'
Run date from 127.0.0.1

client:

> socat - TCP:localhost:5000 <<<"date"
Wed Dec 22 03:37:02 PM CST 2021

Anyway, it is never a good idea to open a port allowing arbitrary connections to execute unconstrained commands.

leodream
  • 11
  • 2