以下の内容はhttps://devlights.hatenablog.com/entry/2025/02/14/073000より取得しました。


/dev/tcp と /dev/udp (bash)

関連記事

GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ

概要

以下、自分用のメモです。たまーに使いたいときにやり方忘れてるのでここにメモメモ。。。

bashに存在する機能で /dev/tcp というのがあります。bash特有の機能なのでzshとかの他のシェルには多分ありません。

リバースシェルを作るときなどによく利用されたりしますね。

名前の通り、仮想的なファイルでTCPネットワーク接続を簡単に実現するための仕組みです。

acc3ssp0int.com

bashのマニュアルにも以下のように記載されています。

$ man bash | grep -F "/dev/tcp" -A 5 -B 12
       Bash handles several filenames specially when they are used in redirections, as described in the following ta‐
       ble.   If the operating system on which bash is running provides these special files, bash will use them; oth‐
       erwise it will emulate them internally with the behavior described below.

              /dev/fd/fd
                     If fd is a valid integer, file descriptor fd is duplicated.
              /dev/stdin
                     File descriptor 0 is duplicated.
              /dev/stdout
                     File descriptor 1 is duplicated.
              /dev/stderr
                     File descriptor 2 is duplicated.
              /dev/tcp/host/port
                     If host is a valid hostname or Internet address, and port is an integer port number  or  service
                     name, bash attempts to open the corresponding TCP socket.
              /dev/udp/host/port
                     If  host  is a valid hostname or Internet address, and port is an integer port number or service
                     name, bash attempts to open the corresponding UDP socket.
/dev/tcp/host/port

という形でパスを作ると、対象エンドポイントへの通信経路として利用出来ます。

仮想ファイルなので、当然ながらファイルシステム上には存在しません。

ポート空いてるかどうかの確認とかに結構便利だったり、簡易にサーバにアクセス試したいときなどに使えたりします。

ポートが空いているかどうかは、例えば以下のようにして確認できます。

timeout 1 bash -c "echo > /dev/tcp/localhost/8888" 2>/dev/null && echo "OPEN" || echo "CLOSED"

複数をループして調べたい場合はこんな感じですね。

for port in 80 443 3306 5432 8888; do
    timeout 1 bash -c "echo > /dev/tcp/localhost/$port" 2>/dev/null && echo "$port:OPEN" || echo "$port:CLOSED"
done

要求と応答を確認したい場合は exec を使って処理します。

# 接続
exec 3<>/dev/tcp/localhost/8888
# 送信
echo "helloworld" >&3
# 受信
cat <&3
# 接続
exec 3>&-

ちなみにですが、/dev/tcp があるということは /dev/udp もあります。使い方も同じです。

試してみる

なんか送受信できるサーバが必要なので、とりあえず以下を用意。ただのエコーサーバ。

package main

import (
        "context"
        "log"
        "net"
        "os"
        "os/signal"
        "sync"
        "time"
)

func init() {
        log.SetFlags(log.Ltime)
}

func main() {
        ln, err := net.Listen("tcp", ":8888")
        if err != nil {
                panic(err)
        }
        log.Println("Starting server at :8888")

        var wg sync.WaitGroup
        sigCtx, sigCxl := signal.NotifyContext(context.Background(), os.Interrupt)
        go func() {
                <-sigCtx.Done()
                log.Println("Shutdown server started")

                sigCxl()
                ln.Close()
        }()

        for {
                conn, err := ln.Accept()
                if err != nil {
                        if sigCtx.Err() != nil {
                                break
                        }

                        log.Fatalln(err)
                }
                log.Printf("Accept client (%s)", conn.RemoteAddr())

                wg.Add(1)
                go func(conn net.Conn) {
                        defer wg.Done()
                        defer conn.Close()

                        buf := make([]byte, 1024)
                        for {
                                b, err := conn.Read(buf)
                                if b == 0 || err != nil {
                                        break
                                }

                                _, err = conn.Write(buf[:b])
                                if err != nil {
                                        break
                                }
                        }

                        log.Printf("Close  client (%s)", conn.RemoteAddr())
                }(conn)
        }

        c := make(chan struct{})
        go func() {
                wg.Wait()
                close(c)
        }()

        select {
        case <-time.After(5 * time.Second):
        case <-c:
        }

        log.Println("Shutdown server completed")
}

サーバを起動しておきます。

$ go build .
$ ./echoserver
13:18:30 Starting server at :8888

別のターミナルで以下を実行します。

$ exec 3<>/dev/tcp/localhost/8888
$ echo "helloworld" >&3
$ cat <&3
helloworld
^C
$ exec 3>&-

サーバ側は以下のような出力となります。

13:18:41 Accept client (127.0.0.1:53310)
13:18:59 Close  client (127.0.0.1:53310)
^C13:19:04 Shutdown server started
13:19:04 Shutdown server completed

ちゃんと接続して送受信できてますね。ファイルディスクリプタを閉じないままだと当然ながら切断されないので注意です。

逆に言うと、接続したままジーッとさせることも簡単。

滅多に利用するない機能ですが、知ってるとどこかで役に立つかもしれません。

参考情報

/dev/tcpの歴史

devices - Which Unix-like system really provides the /dev/tcp special file? - Unix & Linux Stack Exchange

networking - /dev/tcp/hostname/80: as an interactive session? - Super User


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。




以上の内容はhttps://devlights.hatenablog.com/entry/2025/02/14/073000より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14