So, I recently found out about how you can send TCP packets through the /dev/tcp/hostname/port
file, but my main problem here is how I receive them. I want to make a bash script that can host a TCP server, and another script as a TCP client. Some help would be great. I would also like to note that I am doing this as a learning experience, which is why I'm not using ssh
or telnet
as a way to preform the tasks I want.

- 1,968
4 Answers
According to this, no. You can't do it because bash
doesn't attempt to bind(2)
the sockets, but rather only attempts to connect(2)
.
Here are a few ways you might start a netcat
server:
(I use nmap
's ncat
because the GNU nc
hasn't seen an update since 2004)
local $ :|{ ncat -l 9999 --keep-open --allow localhost |
local > PS1='ncat $ ' PS2='ncat > ' dash +m -i 2>&1
local > } 1<>/dev/fd/0
[1] + Running : | { ncat -l 9999 --keep-open --allow localhost | PS1='ncat $ ' PS2='ncat > ' dash +m -i 2>&1; } 1<>/dev/fd/0
I borrow the :|
pipe at the end there - dash
writes out to it, and ncat
reads it because the :
null command certainly has no need of it. It's sometimes easier than mkfifo
.
Anyway, the point is that everything ncat
writes is piped to dash
's standard input, and everything dash
writes is piped to ncat
's standard input. It's not bash
, I know, but I'll get to that. Note though, at least, the +m
option.
local $ ncat localhost 9999
ncat $ echo $0 $$
dash 31231
ncat $ ls -l /dev/fd/[012]
lr-x------ 1 mikeserv mikeserv 64 Dec 11 23:28 /dev/fd/0 -> pipe:[3563412]
l-wx------ 1 mikeserv mikeserv 64 Dec 11 23:28 /dev/fd/1 -> pipe:[3567682]
l-wx------ 1 mikeserv mikeserv 64 Dec 11 23:28 /dev/fd/2 -> pipe:[3567682]
ncat $ ^C
local $
I have to use the +m
because a -m
onitored job-controlled shell - as is generally set to on by default for an -i
nteractive shell - would try to read from its controlling terminal and it would be sent a SIGTTIN
- which would automatically suspend it by default, or else (if it were trying to ignore or block it) kill it.
[1] + Stopped(SIGTTIN) : | { ncat -l 9999 | dash -i 2>&1; } 1<>/dev/fd/0
But there is no terminal here - these are just pipes, as you can see in the ls
output - and so -m
onitoring is a no-go.
bash
, as it happens, doesn't seem to be as flexible about its initial interactive terminal associations, even with readline
disabled.
[1] + Stopped(SIGTTIN) : | { ncat -l 9999 | bash --noediting +m -i 2>&1 ; } 1<>/dev/fd/0
So what to do? Well, we need a pseudo-terminal. Something like what we have in the emulator.
local $ ls -l /dev/fd/[012]
lrwx------ 1 mikeserv mikeserv 64 Dec 11 23:30 /dev/fd/0 -> /dev/pts/0
lrwx------ 1 mikeserv mikeserv 64 Dec 11 23:30 /dev/fd/1 -> /dev/pts/0
lrwx------ 1 mikeserv mikeserv 64 Dec 11 23:30 /dev/fd/2 -> /dev/pts/0
We can get one from the /dev/ptmx
master-device on a linux system (though your user will probably need to be a member of the tty
group if you don't want to chown
it first). If we <
open(2)
the ptmx device a new pseudo-terminal will be created, and if we afterwards unlockpt(3)
the new device file, we can read and write it.
Some time ago I learned there isn't really a simple way to do so with the shell, and so I wrote a little C program for it. You'll find some explanation and simple build instructions (actually just a copy/paste into a shell prompt) at that link - it's the pts
program I use below.
local $ { 9>&- setsid -c -- bash <> "$({ pts &&
local > >&9 ncat -l 9999 -k --allow localhost
local > } <&9 &)" 2>&0 >&2 ; } 9<> /dev/ptmx
local $
There a bash
is started as session leader on the pseudo-terminal which pts
unlocks and names to its standard out - which is substituted for the <>
standard in redirection for bash
. Nothing apparently happens, though, because all of the i/o is going elsewhere - to the new pseudo-terminal - and the only link is the ncat
server started on port 9999. The -i
nteractive switch isn't necessary here - bash
will automatically be interactive on its own pty.
local $ ncat localhost 9999
[mikeserv@desktop ~]$ echo hey
echo hey
hey
[mikeserv@desktop ~]$ ls -l /dev/fd/[012]
ls -l /dev/fd/[012]
lrwx------ 1 mikeserv mikeserv 64 Dec 12 00:27 /dev/fd/0 -> /dev/pts/4
lrwx------ 1 mikeserv mikeserv 64 Dec 12 00:27 /dev/fd/1 -> /dev/pts/4
lrwx------ 1 mikeserv mikeserv 64 Dec 12 00:27 /dev/fd/2 -> /dev/pts/4
[mikeserv@desktop ~]$ ^[[A^[[A
Well, we're nearly there. So we've definitely got a socketized-bash
, but probably you notice the weird double-echo there, and the funny escapes at the last prompt are actually me pressing the ^up arrow key. The problem here is we have two levels of pseudo-terminals - the one on which my ncat
client runs is set to stty echo
the same as the one on which the server runs. And because the client-side terminal is line-oriented, and flushes output once per input newline, and is also printing ctrl-char escapes according to the default stty echoctl
setting, the bash
never receives the ^up arrow key at all, and we only see the funny little escapes.
Ok. We can handle that too.
local $ ncatsh()(
local > ${2+":"} set localhost "${1:?ncatsh(): No port number provided!}"
local > stty=" stty -F'$(tty)'" || unset stty
local > trap " ${stty- ncat '${1##*\'*}' '${2##*\'*}' </dev/null} \
local > ${stty+$(for a in -g 'raw -echo isig intr "^A" quit "" susp ""'
local > do eval "$stty $a";done)}
local > trap - 0 1 2; exit" 0 1 2
local > ncat "$@"
local > )
That function will check if its standard in is a terminal - your local terminal - and if not will just pass-through input to an ncat
client. But if so it will trap
the EXIT, HANGUP, and INTERRUPT signals to restore the terminal state on its standard in when it quits. This is a good thing, because it also alters that state before calling ncat
on standard in.
The local echo is disabled and the local terminal is otherwise set to raw mode - so each keypress is sent to the ncat
server immediately. In fact, all special local-mode keys are disabled except the local intr - which is usually CTRL+C
, but is here configured for CTRL+A
instead - because CTRL+C
will be interpreted by the bash
server.
I'll do ls
again but just by pressing ^up arrow then RETURN
.
local $ ncatsh 9999
[mikeserv@desktop ~]$ ls -l /dev/fd/[012]
lrwx------ 1 mikeserv mikeserv 64 Dec 12 01:00 /dev/fd/0 -> /dev/pts/4
lrwx------ 1 mikeserv mikeserv 64 Dec 12 01:00 /dev/fd/1 -> /dev/pts/4
lrwx------ 1 mikeserv mikeserv 64 Dec 12 01:00 /dev/fd/2 -> /dev/pts/4
[mikeserv@desktop ~]$ ^C
[mikeserv@desktop ~]$ ^C
[mikeserv@desktop ~]$ ^C
[mikeserv@desktop ~]$
local $
You can't see it there because the local echo is disabled when it is pressed, but I pressed CTRL+A
to interrupt the session and come back to the local prompt, at which time all of the local terminal configuration is restored to rights. The bash
server remains, though, and ncatsh 9999
will take me right back if I will it.
-
2Whoa. I'm not that advanced yet. It's kinda stupid that no one has really made a tool for what I'm describing, besides game servers. – SpecialBomb Dec 24 '15 at 08:45
-
2I must hereby ignore the add-comment tooltip and offer you my wholehearted *thanks* for these gems. I just stumbled on this (while doublechecking if bash's /dev/tcp can bind()) and learned like fifty new things. I love working with the shell, this is great! Thanks again! – i336_ Mar 27 '17 at 13:25
To ease your job, try using the "netcat" tool. It may be used as server or client, and is able to send and receive arbitrary data.
No need to bother with low-level stuff then..
#server listening on port 8000/tcp
$>netcat -l 8000
#client sending stuff to localhost 8000/tcp
$>netcat localhost 8000
blah

- 2,188
-
Thats good, but my problem lies within netcat, which is why I wanted to do stuff over TCP. If you can help me with netcat, that would be nice too. I want to have a nc server echo back data when I send a command. for example,
CMD> ls
, and then the computer I'm connected to will runls
, send the data over to the nc client, then wait for another command. I tried doing this in a bash script, but I couldn't figure out how to send and recieve data over nc at the same time without disconnecting and reconnecting. – SpecialBomb Dec 10 '15 at 18:00 -
You just described
telnet
. You should usetelnet
, or usessh
if you want a secure connection. – ewatt Dec 10 '15 at 23:49 -
@MyNameIsBoring Yes, I use
ssh
, but one of the goals (which I should edit in) was to make my own kind of remote connection program as a learning experience. I haven't usedtelnet
that much, mostly because of how weird it is to use, in my opinion. – SpecialBomb Dec 11 '15 at 23:27
bash
's virtual /dev/tcp/host/port
files (a ksh feature initially) can only be used to connect a socket to a TCP port.
If you want to do more, like creating a listening socket and spawning accepting sockets from that, that can't be done with redirection.
zsh
has support for that via its zsh/net/tcp
module and ztcp
builtin.
zmodload zsh/net/tcp
handle-connection() (
IFS= read -ru4 line || exit
print -r "Got: $line"
print -ru4 "Hello $line"
ztcp -c 4
)
ztcp -l -d 3 1234 # create a TCP listening socket on port 1234 on fd 3
while ztcp -ad4 3 # accept connection on fd 4
do handle-connection & exec 4>&-
done
It's still limited in that you can't specify which address to bind on or the size of the listen queue, or shutdown only one direction, or set socket options... It also doesn't do UDP or SCTP (you can do Unix domain sockets though with the zsocket
command).
If you want anything fancier or if you don't have zsh, you could use socat
. For instance, here with bash
exported functions:
handle_connection() {
IFS= read -r line &&
printf 'Hello %s\n' "$line"
}
export -f handle_connection
socat tcp-listen:1234,reuseaddr,fork 'exec:bash -c handle_connection'
With socat
, the possibilities are endless, see the man page for details.
For instance, for the server to spawn a shell session:
socat tcp-listen:1234,reuseaddr,fork exec:bash,pty,ctty,setsid,stderr,sane
On the client side, you'd connect as:
socat -,raw,echo=0 tcp:host:1234

- 544,893
The easiest way is to create your own xinetd
service like this:
/etc/services
:
...
foo 500/tcp
...
/etc/xinetd.d/foo
:
service foo
{
disable = no
bind = 127.0.0.1
socket_type = stream
protocol = tcp
log_on_failure += USERID
server = /usr/local/lib/foo.sh
user = il
instances = UNLIMITED
wait = no
log_on_success =
}
/usr/local/lib/foo.sh
:
read ...
...
echo ...
Thus your shell script gets executed for each client connection with stdin/stdout attached directly to the socket.

- 2,051
cat </dev/tcp/hostname/port
(for instance to output to the console)? If that's not what you want please add more details about what exactly you need. – Marco Dec 10 '15 at 05:15