결함이 있는 응용 프로그램이 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
마지막으로 글루 스크립트는 작업 편의성을 위해 이전 두 스크립트를 사용합니다. 동일한 소켓을 공유하는 여러 프로세스(또는 스레드)에 쓸데없이 연결되는 것을 방지합니다.
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 소켓을 사용하여 두 개의 프로세스를 생성합니다
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는 네트워크 네임스페이스별로가 아니라 전역적으로 고유합니다. 이를 증명하려고 시도하지는 않았지만 스크립트가 더 단순해졌습니다.