7

This question already has an answer here:

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.

marked as duplicate by Jakuje, Anthon, MelBurslan, chaos, Jeff Schaller Apr 12 '16 at 19:38

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

  • @Mikel Nothing really. I honestly have no idea what im doing, and I was going to piece together the information I got. – SpecialBomb Dec 10 '15 at 4:53
  • 1
    What's wrong with 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 5:15
  • 1
    I don't think bash gives a syntax to listen on ports. Use netcat. – Arthur2e5 Dec 12 '15 at 2:01
  • TCP doesn't do packets either, but streams. – LawrenceC Jan 5 '16 at 19:55
8

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 -monitored job-controlled shell - as is generally set to on by default for an -interactive 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 -monitoring 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 -interactive 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.

  • 2
    Whoa. 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 8:45
  • 2
    I 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
3

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
  • 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 run ls, 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 use telnet, or use ssh 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 used telnet that much, mostly because of how weird it is to use, in my opinion. – SpecialBomb Dec 11 '15 at 23:27
3

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
1

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.

Not the answer you're looking for? Browse other questions tagged or ask your own question.