web-dev-qa-db-ja.com

TCPサーバーをbashで書く

それで、私は最近、TCPパケットを/dev/tcp/hostname/portファイルですが、ここでの主な問題は、それらを受け取る方法です。 TCPサーバーをホストできるbashスクリプトと、TCPクライアントとして別のスクリプトを作成します。いくつかの助けが必要です。また、私はこれを学習体験として行っていることに注意してください。そのため、必要なタスクを実行する方法としてsshまたはtelnetを使用していません。

7
SpecialBomb

this によれば、いいえ。 bashbind(2) ソケットを試行せず、 connect(2) を試行するだけなので、これを行うことはできません。

netcatサーバーを起動する方法をいくつか紹介します。

(私は nmapncat を使用します-- GNU nc は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

私はそこで:|パイプを借りています-: nullコマンドには確かに必要がないため、dashがそれに書き込み、ncatがそれを読み取ります。 mkfifoよりも簡単な場合があります。

とにかく、ポイントは、ncatが書き込むすべてのものがdashの標準入力にパイプ処理され、dashが書き込むすべてがncatの標準入力にパイプ処理されることです。それはbashではありませんが、私はそれを理解します。ただし、少なくとも+mオプションには注意してください。


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 $ 

+mを使用する必要があるのは、-monitoredジョブ制御シェル--interactive Shellのデフォルトで通常オンに設定されているように、その制御端末から読み取ろうとするためです。 SIGTTINが送信されます-デフォルトで自動的に一時停止します。それ以外の場合は(無視またはブロックしようとした場合)強制終了します。


[1] + Stopped(SIGTTIN) : | { ncat -l 9999 | dash -i 2>&1; } 1<>/dev/fd/0

しかし、ここにはターミナルはありません-lsの出力でわかるように、これらは単なるパイプです-したがって、-monitoringは実行できません。

bashは、たとえ発生しても、readlineが無効になっていても、最初のインタラクティブな端末の関連付けに関して柔軟ではないようです。


[1] + Stopped(SIGTTIN) : | { ncat -l 9999 | bash --noediting +m -i 2>&1 ; } 1<>/dev/fd/0

じゃあ何をすればいいの?まあ、私たちは疑似端末が必要です。エミュレータにあるようなもの。


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

Linuxシステムの/dev/ptmxマスターデバイスから取得できます(ただし、最初にttyを使用したくない場合は、ユーザーがchownグループのメンバーである必要があります。 )<open(2) ptmxデバイスの場合、新しい疑似端末が作成され、その後 unlockpt(3) 新しいデバイスファイルでは、読み書きできます。

少し前に、シェルでこれを行う簡単な方法はないことを知ったので、 私はそのための小さなCプログラムを作成しました です。いくつかの説明と簡単なビルド手順(実際にはシェルプロンプトへのコピー/貼り付け)がそのリンクにあります。これは、以下で使用するptsプログラムです。


local $ { 9>&- setsid -c -- bash <> "$({ pts && 
local >   >&9  ncat -l 9999 -k --allow localhost
local > } <&9  &)" 2>&0 >&2 ; } 9<> /dev/ptmx
local $

bashが疑似ターミナルでセッションリーダーとして開始され、ptsがロック解除し、その標準出力に名前を付けます。これは、bashのリダイレクトで<>標準の代わりに使用されます。ただし、すべてのI/Oが別の場所(新しい疑似端末)に移動し、ポート9999で開始されたncatサーバーが唯一のリンクであるため、何も起こりません。ここでは、-interactiveスイッチは必要ありません- bashは自動的に独自の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

まあ、もうすぐそこです。つまり、確実にsocketized -bashを取得しましたが、おそらく奇妙なダブルエコーがあり、最後のプロンプトでの面白いエスケープは、実際には^上矢印キーを押すことです。ここでの問題は、2つのレベルの疑似端末があることです-私のncatクライアントが実行されるものは、サーバーが実行されるものと同じstty echoに設定されます。また、クライアント側の端末は行指向であり、入力改行ごとに1回出力をフラッシュし、デフォルトのstty echoctl設定に従ってctrl-charエスケープも出力しているため、bashは^上矢印キーをまったく受け取りません、そして私たちは面白い小さなエスケープだけを見ます。

OK。それにも対応できます。


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 > )

その関数は、その標準入力がターミナル(ローカルターミナル)であるかどうかをチェックし、そうでない場合は、ncatクライアントへの入力をパススルーするだけです。しかし、そうであればtrap[〜#〜] exit [〜#〜][〜#〜] hangup [〜#〜]、および[〜#〜] interrupt [〜#〜]シグナルは、終了時に標準でターミナル状態を復元します。標準入力でncatを呼び出す前に状態を変更するので、これは良いことです。

ローカルエコーは無効になり、ローカルターミナルはそれ以外の場合はrawモードに設定されるため、キーを押すたびにncatサーバーにすぐに送信されます。実際、すべての特別なローカルモードキーは無効になっています例外ローカルintr-通常CTRL+C、ただしここではCTRL+Aに設定されています-CTRL+Cは、bashサーバーによって解釈されます。

もう一度lsを行いますが、^上矢印キーを押してから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 $

ローカルエコーが押されていると無効になっているため、そこには表示されませんが、CTRL+Aを押してセッションを中断し、ローカルプロンプトに戻ります。その時点で、すべてのローカル端末構成が権利に復元されます。ただし、bashサーバーは残ります。そうすれば、ncatsh 9999がすぐに戻ってきます。

8
mikeserv

bashの仮想/dev/tcp/Host/portファイル(最初はksh機能)はconnectソケットからTCPポートへのソケット)にのみ使用できます。

リスニングソケットを作成したり、ソケットを受け入れたりするなど、さらに多くのことを実行したい場合は、リダイレクトではできません。

zshzsh/net/tcpモジュールとztcp組み込み。

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

バインドするアドレスやリッスンキューのサイズを指定したり、一方向のみをシャットダウンしたり、ソケットオプションを設定したりすることはできませんが、UDPやSCTPは実行できません(UNIXでも実行できます)。ただし、zsocketコマンドを使用したドメインソケット)。

より洗練されたものが必要な場合、またはzshがない場合は、socatを使用できます。たとえば、ここではbashエクスポート関数を使用します。

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'

socatを使用すると、可能性は無限です。詳細については、manページを参照してください。

たとえば、サーバーがシェルセッションを生成する場合:

socat tcp-listen:1234,reuseaddr,fork exec:bash,pty,ctty,setsid,stderr,sane

クライアント側では、次のように接続します。

socat -,raw,echo=0 tcp:Host:1234
3

仕事を楽にするために、「netcat」ツールを使用してみてください。サーバーまたはクライアントとして使用でき、任意のデータを送受信できます。

低レベルのものを気にする必要はありません。

#server listening on port 8000/tcp
$>netcat -l 8000

#client sending stuff to localhost 8000/tcp
$>netcat localhost 8000
blah
3
gerhard d.

最も簡単な方法は、次のように独自のxinetdサービスを作成することです。

/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 ...

したがって、シェルスクリプトは、stdin/stdoutがソケットに直接接続されているクライアント接続ごとに実行されます。

1
basin