バインディング()は実行しますが、受信()は実行しないアプリケーション/ポートをどのように見つけることができますか?

バインディング()は実行しますが、受信()は実行しないアプリケーション/ポートをどのように見つけることができますか?

障害のあるアプリケーションがbind()TCPソケットを使用して特定のポートを呼び出したがフォローしていないP場合、listen()そのポートはP開いているポートにリストされません。つまり、netstat表示されませんssls /proc/net/tcp、ポートが占有され、他のアプリケーションでは使用できません。そのようなアプリケーションとポートを見つけるための合理的な方法はありますか?

ベストアンサー1

より適切なものが出るまで、bind(2)TCPソケットで使用されているプロセスを見つけるために間違いなく非産業的な方法で試してみますが、両方が見つからずにlisten(2)バインドconnect(2)されたTCPアドレスが何であるかを示す答えがあります。

必要getfattrattrほとんどのディストリビューションでは、指定されたパッケージに含まれています。カーネル >= 3.7非TCPソケットをフィルタリングし、インストールを最小限に抑えます。gdb(例えばDebianの場合:gdb-minimal)。開発環境は必要ありません。次のように実行する必要がありますユーザー(そうでなければ同じユーザーに関する情報のみを見つけることができますが、コンテナ全体では機能しません。)の最後の注意事項を参照してください。


要素:

  • 最初のシェルスクリプトは機能の一部を模倣しますlsofが、この特定の場合にのみ適用されます。すべてのプロセスでソケットFDを検索します。属性を持つソケットの場合TCPまたはTCPv6(ファイルメタ属性としてsystem.sockprotoname使用可能)getfattr、見つかったとおりにlsof使用されます。getxattr(2)これは少なくともTCPソケットであることを示しています。)ソックスファイルシステム擬似ファイルシステム)inodeはそのネットワークネームスペースにありますtcptcp6 プロセスファイルに存在しない場合、pid、fd、およびinodeは3つの候補としてマークされます。このスクリプトだけで「欠陥のある」プロセスを見つけて一覧表示できます。

    findbadtcpprocs.sh:

    #!/bin/sh
    
    find /proc -mindepth 1 -maxdepth 1 -name '[1-9]*' |
        xargs -I{} find {}/fd -follow -type s 2>/dev/null |
            while read procfd; do
                type=$(getfattr --absolute --only-values -L -n system.sockprotoname $procfd | tr '\0' '\n')
                if [ "$type" = "TCP" -o "$type" = "TCPv6" ]; then
                    inode=$(stat -L -c %i $procfd)
                    pid=$(echo $procfd | cut -d/ -f3)
                    if awk '$10 == inode { exit 1 }' inode=$inode /proc/$pid/net/tcp /proc/$pid/net/tcp6; then
                        fd=$(echo $procfd | cut -d/ -f5)
                        echo $pid $fd $inode
                    fi
                fi
            done 
    

    このスクリプトは、追加情報なしで候補プロセスのみを見つけるためにスタンドアロンで使用できます。

  • gdbその後、正しい値を提供する必要があるスクリプトがあります。FD情報。これは候補プロセスに接続され(最初にいくつかのメモリを割り当てて)runを実行し、getsockname(2)バインドされたソケットを表示し(割り当てられたリソースを解放し)、プロセスを解放します。

    getsockname.gdb:

    set $malloc=(void *(*)(long long)) malloc
    set $ntohs=(unsigned short(*)(unsigned short)) ntohs
    p $malloc(64)
    p $malloc(4)
    set *(long *)$2=64
    p (int) getsockname($fd,$1,$2)
    set logging file /dev/stdout
    set logging on
    if *((short *) $1) == 2
        set $ip=(unsigned char *) ($1+4)
        printf "%hu.%hu.%hu.%hu",$ip[0],$ip[1],$ip[2],$ip[3]
    else
        if *((short *) $1) == 10
            set $ip6=(unsigned short *) ($1+8)
            printf "[%hx:%hx:%hx:%hx:%hx:%hx:%hx:%hx]",$ntohs($ip6[0]),$ntohs($ip6[1]),$ntohs($ip6[2]),$ntohs($ip6[3]),$ntohs($ip6[4]),$ntohs($ip6[5]),$ntohs($ip6[6]),$ntohs($ip6[7])
        end
    end
    printf ":%hu\n",$ntohs(*(unsigned short *)($1+2))
    set logging off
    call (void) free($2)
    call (void) free($1)
    quit
    
  • 最後に、グルスクリプトは作業の便宜のために前の2つのスクリプトを使用します。同じソケットを共有する複数のプロセス(またはスレッド)に無駄に接続するのを防ぎます。

    result.sh:

    #!/bin/sh
    
    oldinode=-1
    ./findbadtcpprocs.sh | sort -s -n -k 3 | while read pid fd inode; do
        printf '%d\t%d\t%d\t' $pid $fd $inode
        if [ $inode -ne $oldinode ]; then
            socketname=$(gdb -batch-silent -p $pid -ex 'set $fd'=$fd -x ./getsockname.gdb 2>/dev/null) || socketname=FAIL
            oldinode=$inode
        fi
        printf '%s\n' "$socketname"
    done
    

    すべてを有効にするには、次のコマンドを実行します。

    chmod a+rx findbadtcpprocs.sh result.sh
    ./result.sh
    
  • ボーナスとして、Cソースコードのシンプルなプレーヤーは、同じTCPソケットを使用せずに同じTCPソケットを使用して2つのプロセスを生成しますlisten(2)。使用法:gcc -o badtcpbind badtcpbind.cおよび./badtcpbind 5555

    badtcpbind.c:

    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    
    #include <strings.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    int main(int argc, char *argv[])
    {
        int sockfd;
        struct sockaddr_in myaddr;
        if (argc < 2) {
            exit(2);
        }
        if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
            perror("socket");
            exit(1);
        }
        bzero(&myaddr, sizeof myaddr);
        myaddr.sin_family = AF_INET;
        myaddr.sin_addr.s_addr = INADDR_ANY;
        myaddr.sin_port = htons(atoi(argv[1]));
        if (bind(sockfd, (struct sockaddr *) &myaddr, sizeof myaddr) < 0) {
            perror("bind");
            exit(1);
        }
    
    #if 0
         listen(sockfd,5);
    #endif
    
        fork();
        sleep(9999);
    }
    

例:

# ./badtcpbind 5555 &
[1] 330845
# ./result.sh 
108762  20  303507  0.0.0.0:0
330845  3   586443  0.0.0.0:5555
330846  3   586443  0.0.0.0:5555

(はい、不明な理由でlibvirtdTCPソケットを生成するプロセスはここに表示されますが、ソケットは使用されず、結果の最初の行にキャプチャされます。)


指示:

  • 読みやすさと効率性を向上させるには、シェルよりも優れた言語を使用する必要があります。

  • 確かにに比べてより活力があふれますlsof

  • ここで行われた方法で実行中のプロセスに接続する際に問題があります。

    • 静的にリンクされたバイナリでは機能しません(malloc()関数や特定のシンボル定義が利用できない場合)。
    • デバッグ情報は利用できないため、ほとんどの関数には明示的な範囲があり、変更しないとすべての環境で実行されない可能性があります(AMD64カーネル 5.10.x アーキテクチャ、Debian Bullseye、Debian 10、CentOS 7 ユーザー空間).
    • さらに、通常のglibc以外のlibcs​​とリンクされているバイナリはそのまま機能しない可能性があります。
    • 邪魔になり、脆弱な(特にマルチスレッド)アプリケーションがクラッシュする可能性があります。確認が完了していません(例:malloc(3)またはgetsockname(2)失敗)。
  • 考慮すべき最後のスクリプトソックスファイルシステムinodeはネットワークネームスペースではなくグローバルに一意です。これを証明しようとはしませんでしたが、スクリプトはより簡単になりました。

おすすめ記事