Bash tcp redirection end of transmission

bashshell-scripttcpxinetd

I made a simple network service with xinetd which reads a string from tcp socket and outputs its encoded view. Original binary(qrencode) just reading stdin.

It works fine when I use it with netcat as echo string | nc <ip> <port>

But it doesn't answer when I try to use it via bash tcp redirection.

exec 7<>/dev/tcp/<ip>/<port>
echo string >&7
cat <&7

It waits forever. I tried echoing \004, cat /dev/null to this fd and no luck.
How can I make it working?

Best Answer

That inetd service or yours is probably waiting for EOF before exiting (and then closing the connection).

To do that, you'd need the client to shutdown the sending side of the socket while still keeping the receive side open. That's what nc does when it detects EOF on its stdin.

The /dev/tcp/host/port virtual interface of bash (copied from ksh) doesn't allow that. Even zsh's ztcp more flexible alternative doesn't have the ability to asymmetrically shutting down connections.

You could rely on a timeout instead, but that would be unreliable. So best it to keep relying on dedicated utilities like nc or socat (possibly with a coproc to have a similar interface), or use a programming language with a proper network API (with an interface to shutdown()).

The 0x04 character (^D) only means EOF for terminal devices when the line discipline is in icanon mode (implements a crude line editor). So for that to work you'd have to insert a pseudo-terminal in between the socket created by xinetd and your service. For instance, by starting it with:

socat - exec:'your-service',pty,raw,icanon,echo=0,discard=,lnext=,werase=,kill=,start=,stop=,rprnt=,erase=,fdout=3

instead of

your-service

Then you'd need to send:

printf 'text\n\4' >&7

Or:

printf 'text\4\4' >&7

for your-service to see the end of input.

Because of that line editor, your-service will only see input when you send newline or ^D characters (we've disabled all the other editing characters like werase, erase, kill above and the ^C, ^Z... handling is also disabled as part of raw).

So you might as well insert an input pre-processor instead that quits upon that 0x4 character like:

awk -v RS='\4' -v ORS= '{print; exit}' | your-service

(note that that awk can't be mawk as mawk insists in not processing its input as long as it's not received a full buffer or eof).

Related Question