障害のあるアプリケーションがbind()
TCPソケットを使用して特定のポートを呼び出したがフォローしていないP
場合、listen()
そのポートはP
開いているポートにリストされません。つまり、netstat
表示されませんss
がls /proc/net/tcp
、ポートが占有され、他のアプリケーションでは使用できません。そのようなアプリケーションとポートを見つけるための合理的な方法はありますか?
ベストアンサー1
より適切なものが出るまで、bind(2)
TCPソケットで使用されているプロセスを見つけるために間違いなく非産業的な方法で試してみますが、両方が見つからずにlisten(2)
バインドconnect(2)
されたTCPアドレスが何であるかを示す答えがあります。
必要getfattr
attr
ほとんどのディストリビューションでは、指定されたパッケージに含まれています。カーネル >= 3.7非TCPソケットをフィルタリングし、インストールを最小限に抑えます。gdb
(例えばDebianの場合:gdb-minimal
)。開発環境は必要ありません。次のように実行する必要があります根ユーザー(そうでなければ同じユーザーに関する情報のみを見つけることができますが、コンテナ全体では機能しません。)の最後の注意事項を参照してください。
要素:
最初のシェルスクリプトは機能の一部を模倣します
lsof
が、この特定の場合にのみ適用されます。すべてのプロセスでソケットFDを検索します。属性を持つソケットの場合TCP
またはTCPv6
(ファイルメタ属性としてsystem.sockprotoname
使用可能)getfattr
、見つかったとおりにlsof
使用されます。getxattr(2)
これは少なくともTCPソケットであることを示しています。)ソックスファイルシステム擬似ファイルシステム)inodeはそのネットワークネームスペースにありますtcp
。tcp6
プロセスファイルに存在しない場合、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
(はい、不明な理由でlibvirtd
TCPソケットを生成するプロセスはここに表示されますが、ソケットは使用されず、結果の最初の行にキャプチャされます。)
指示:
読みやすさと効率性を向上させるには、シェルよりも優れた言語を使用する必要があります。
確かにに比べてより活力があふれます
lsof
。ここで行われた方法で実行中のプロセスに接続する際に問題があります。
- 静的にリンクされたバイナリでは機能しません(
malloc()
関数や特定のシンボル定義が利用できない場合)。 - デバッグ情報は利用できないため、ほとんどの関数には明示的な範囲があり、変更しないとすべての環境で実行されない可能性があります(AMD64カーネル 5.10.x アーキテクチャ、Debian Bullseye、Debian 10、CentOS 7 ユーザー空間).
- さらに、通常のglibc以外のlibcsとリンクされているバイナリはそのまま機能しない可能性があります。
- 邪魔になり、脆弱な(特にマルチスレッド)アプリケーションがクラッシュする可能性があります。確認が完了していません(例:
malloc(3)
またはgetsockname(2)
失敗)。
- 静的にリンクされたバイナリでは機能しません(
考慮すべき最後のスクリプトソックスファイルシステムinodeはネットワークネームスペースではなくグローバルに一意です。これを証明しようとはしませんでしたが、スクリプトはより簡単になりました。