@luzluna - luz + luna
네트워크상에서 작업을 하다보면 가끔씩은 정말 무엇이 어떻게 돌아가고 있는지 두 눈으로 확인하고 싶은 욕구가 (치?)밀어오를 때가 많곤합니다. 응용과 시스템 그리고 원격지 사이에서 그 누구도 잘못이 없어보일 때 범인을 찾으려면 역시 네트워크 패킷을 직접 확인하는 것보다 정확한 것은 없죠. tcpdump나 와이어샤크 같은 유명한 도구도 있지만 직접 펄 코드와 연동해서 디버깅하는 것이 훨씬 편리할 때가 많습니다. pcap 라이브러리는 tcpdump 팀이 제작한 라이브러리로 tcpdump와 와이어샤크등의 패킷 캡쳐 프로그램의 핵심이라고 할 수 있습니다. pcap을 사용하면 펄에서는 손쉽게 패킷 캡쳐를 펄 프로그램과 연동할 수 있습니다.
pcap 관련 라이브러리를 컴파일하고 NetPacket 모듈에 의존성이 있는 Net::Libdnet 모듈을 컴파일 하기 위해 설치해야 할 도구가 있습니다. 데비안 계열의 리눅스를 사용하고 있다면 다음 명령을 이용해서 패키지를 설치합니다.
$ sudo apt-get install libpcap-dev libdumbnet-dev
필요한 모듈은 다음과 같습니다.
직접 CPAN을 이용해서 설치한다면 다음 명령을 이용해서 모듈을 설치합니다.
$ sudo cpan Net::Pcap NetPacket
사용자 계정으로 모듈을 설치하는 방법을 정확하게 알고 있거나 perlbrew를 이용해서 자신만의 Perl을 사용하고 있다면 다음 명령을 이용해서 모듈을 설치합니다.
$ cpan Net::Pcap NetPacket
시스템에 따라 NetPacket
모듈 설치에 실패하는 경우도 있습니다.
NetPacket
모듈 설치시 실패하는 경우는 대부분 Net::Libdnet
모듈 설치 실패로 발생합니다.
... /home/askdna/perl5/perlbrew/perls/perl-5.18.1/bin/perl /home/askdna/.perlbrew/libs/perl-5.18.1@advent/lib/perl5/ExtUtils/xsubpp -typemap /home/askdna/perl5/perlbrew/perls/perl-5.18.1/lib/5.18.1/ExtUtils/typemap -typemap typemap Libdnet.xs > Libdnet.xsc && mv Libdnet.xsc Libdnet.c cc -c -I/usr/include -D_REENTRANT -D_GNU_SOURCE -fno-strict-aliasing -pipe -fstack-protector -I/usr/local/include -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -O2 -DVERSION=\"0.98\" -DXS_VERSION=\"0.98\" -fPIC "-I/home/askdna/perl5/perlbrew/perls/perl-5.18.1/lib/5.18.1/x86_64-linux-thread-multi/CORE" Libdnet.c Libdnet.xs:37:18: fatal error: dnet.h: 그런 파일이나 디렉터리가 없습니다 #include <dnet.h> ^ compilation terminated. make: *** [Libdnet.o] 오류 1 ... Installing /home/askdna/.perlbrew/libs/perl-5.18.1@advent/lib/perl5/x86_64-linux-thread-multi/.meta/Net-IPv6Addr-0.2/install.json Installing /home/askdna/.perlbrew/libs/perl-5.18.1@advent/lib/perl5/x86_64-linux-thread-multi/.meta/Net-IPv6Addr-0.2/MYMETA.json -> FAIL Installing the dependencies failed: Module 'Net::Libdnet' is not installed -> FAIL Bailing out the installation for Net-Packet-3.27. 7 distributions installed
이는 dnet 라이브러리가 최근 dumbnet으로 이름을 변경하며 헤더 파일의 이름이 달라져 발생하는 문제입니다.
관련 시스템 개발 라이브러리를 설치했는데도 dnet.h
헤더 파일을 찾지 못한다면 모듈을 약간 수정해주어야 합니다.
$ wget http://cpan.metacpan.org/authors/id/G/GO/GOMOR/Net-Libdnet-0.98.tar.gz $ tar xvzf Net-Libdnet-0.98.tar.gz $ cd Net-Libdnet-0.98 $ perl -pi.bak -e 's/#include <dnet.h>/#include <dumbnet.h>/' Libdnet.xs
제대로 변경되었는지 확인해보죠.
$ diff -urN Libdnet.xs.bak Libdnet.xs
다음처럼 변경되었다면 성공입니다.
--- Libdnet.xs.bak 2013-12-05 10:34:56.100728050 +0900 +++ Libdnet.xs 2013-12-05 10:36:01.688559294 +0900 @@ -34,7 +34,7 @@ #include "XSUB.h" #include <stdio.h> -#include <dnet.h> +#include <dumbnet.h> #ifdef DNET_BLOB_H typedef blob_t Blob;
빌드 및 설치를 진행합니다. 자신의 펄 환경에 따라 사용자 권한 또는 관리자 권한으로 모듈을 설치하세요.
$ perl Makefile.PL $ make $ make test $ make install # 또는 sudo make install
다음은 패킷 캡쳐에 사용할 장치를 화면에 출력하는 간단한 프로그램입니다.
#!/usr/bin/env perl # # FILE: pcap.pl # use v5.14; use strict; use warnings; use Net::Pcap; my $err = q{}; my $dev = pcap_lookupdev(\$err); # find a device say $dev;
간단하죠? 실행해볼까요?
$ perl pcap.pl $
어라! 명령줄에 아무 것도 출력되지 않는군요.
pcap 모듈은 네트워크 장비에 직접 접근하기 때문에 관리자 권한이 있어야 제대로 동작합니다. 관리자 권한으로 실행해보죠.
$ sudo perl pcap.pl eth0 $
잘 되는군요! :-)
앞서 모듈 설치시에 잠깐 언급했던 것처럼 여러분이 사용자 계정으로 모듈을 설치하는 방법을 정확하게 알고 있거나 perlbrew를 이용해서 자신만의 Perl을 사용하고 있다면 지금쯤이면 꽤나 혼란스러울 것입니다. 아마도 다음과 같은 오류를 출력하며 간단한 예제 프로그램이 여전히 동작하지 않을테니까요. :)
$ sudo perl pcal.pl Can't locate Net/Pcap.pm in @INC (you may need to install the Net::Pcap module) (@INC contains: /etc/perl /usr/local/lib/perl/5.18.1 /usr/local/share/perl/5.18.1 /usr/lib/perl5 /usr/share/perl5 /usr/lib/perl/5.18 /usr/share/perl/5.18 /usr/local/lib/site_perl .) at pcap.pl line 1. BEGIN failed--compilation aborted at pcap.pl line 1. $
놀라지 마세요. 모든 문제와 문제의 해답은 경고나 오류 메시지에 담겨져 있습니다.
관리자 권한으로 실행하기 위해 관리자 계정으로 로그인 하거나 sudo
를 이용해서
프로그램을 실행하는 순간 지금까지 사용하던 펄이 아닌 관리자 계정의 펄을 사용하게 되기 때문입니다.
여러분이 시스템의 펄과는 별개의 펄을 사용하고 있었다면 당연히 모듈도 별개의 펄을 위해 설치했을 것입니다.
어쩌면 필요한 모듈을 찾지 못하는 것은 당연한 결과죠.
지금까지 사용하던 펄이 아닌 관리자 계정의 펄이 어떻게 다른지 직접 확인해보죠.
$ which perl /home/user/perl5/perlbrew/perls/perl-5.18/bin/perl $ sudo which perl /usr/bin/perl $
문제를 해결하기 위한 몇 가지 방법이 있는데 가장 간단한 방법부터 가장 정확한 방법까지 하나씩 짚어보죠.
DON'T DO THAT!
이 방법은 쉽고 간단해서 마약과 같습니다. 부득이한 경우가 아니라면 추천하지 않습니다.
sudo
로 실행시 펄의 절대 경로 지정sudo
로 실행 시 두 번째 인자로 perl
을 입력하죠.
$ sudo perl pcal.pl
이 부분에 착안을 하면 관리자 권한으로 실행을 하되 자신의 계정에 설치했던 펄을 사용할 수 있도록 전체 경로를 지정하면 설치했던 모듈을 사용하는 데 지장이 없을 것입니다.
$ sudo /home/user/perl5/perlbrew/perls/perl-5.18/bin/perl pcap.pl eth0 $
잘 되죠?
그런데 매번 실행할때마다 저 복잡한 라인을 다 쓰려면 꽤나 번거롭습니다.
유닉스 계열에서 스크립트는 대부분 쉬뱅 라인을 지정합니다.
실행 권한이 있는 스크립트는 파일의 가장 첫 줄인 쉬뱅 라인을 읽어
자신을 실행시킬 프로그램을 찾습니다.
보통 확장성 있는 펄 프로그램을 작성하기 위해 쉬뱅 라인은 #!/usr/bin/env perl
로
작성하는 것을 선호하지만 이 경우 펄의 정확한 절대 경로로 설정할 경우
sudo
로 실행한다 하더라도 원하는 펄로 스크립트를 구동할 수 있습니다.
쉬뱅 라인을 바꿔보죠.
#!/home/user/perl5/perlbrew/perls/perl-5.18/bin/perl # # FILE: pcap.pl # use v5.14; use strict; use warnings; use Net::Pcap; my $err = q{}; my $dev = pcap_lookupdev(\$err); # find a device say $dev;
그리고 스크립트에 실행 권한을 줍니다.
$ chmod +x pcap.pl
실행해볼까요?
$ sudo ./pcap.pl eth0 $
역시 잘 동작합니다. :)
이번 방법은 조금 어렵지만 리눅스를 사용한다면 제일 확실한 방법입니다. 단 시스템과 파일의 권한과 pcap에 대해서 어느 정도 이해할(하려 노력할) 필요가 있습니다. :)
모든 문제의 시작은 pcap 라이브러리가 네트워크 장치에 접근할 때 관리자 권한이 필요하기 때문에 발생합니다. 더 정확히 표현하면 관리자 권한은 모든 장치에 접근할 수 있기 때문에 관리자 권한을 획득해서 프로그램을 실행하는 것은 종종 가장 간단한 해결책이며 유일한 해결책이기도 합니다. 다행히 pcap은 선택의 여지가 있는데 pcap을 사용할 바이너리에 적절한 권한을 부여한다면 사용자 권한이 없이도 pcap 관련 도구를 사용할 수 있습니다.
현재 사용하는 펄의 경로를 우선 확인합니다.
$ which perl /home/user/perl5/perlbrew/perls/perl-5.18/bin/perl
상기 경로의 펄에 적절한 권한을 추가합니다.
$ sudo setcap cap_net_raw,cap_net_admin=eip /home/user/perl5/perlbrew/perls/perl-5.18/bin/perl
권한이 제대로 추가되었는지 확인합니다.
$ sudo getcap /home/user/perl5/perlbrew/perls/perl-5.18/bin/perl /home/user/perl5/perlbrew/perls/perl-5.18/bin/perl = cap_net_admin,cap_net_raw+eip
setcap
과 getcap
유틸리티가 없을 경우 데비안 계열의 리눅스를 사용하고 있다면
다음 명령을 이용해서 패키지를 설치합니다.
$ sudo apt-get install libcap2-bin
파일 관련 권한 기능이 제대로 동작하려면 커널에 capability 기능이 포함되어 있어야 합니다.
리눅스 커널 2.6.33 이후의 경우 커널에 포함되어 있으며 이전 커널의 경우
CONFIG_SECURITY_FILE_CAPABILITIES
설정을 활성화 시키고 빌드되어 있어야 합니다.
해당 기능을 활성화하기 위해 부팅 커널의 매개변수 옵션에 file_caps=1
을 추가합니다.
데비안의 경우 /etc/default/grub
파일에서 GRUB_CMDLINE_LINUX_DEFAULT
값을 조정합니다.
--- a/etc/default/grub --- b/etc/default/grub -GRUB_CMDLINE_LINUX_DEFAULT="quiet" +GRUB_CMDLINE_LINUX_DEFAULT="quiet file_caps=1"
수정 후 다음 명령을 실행해서 grub 설정을 갱신합니다.
$ sudo update-grub
커널 매개변수 옵션을 변경했다면 재부팅 후 다시 프로그램을 실행합니다.
$ ./pcap.pl eth0 $
관리자 권한이 없이도 pcap 관련 함수가 제대로 호출되어 결과를 반환합니다.
권한과 관련한 자세한 내용은 man capabilities
문서를 참조하세요. :)
먼 길을 돌아 Net::Pcap
모듈을 사용할 준비가 되었으니 이제 패킷을 캡쳐해봐야겠죠?
#!/usr/bin/env perl # # FILE: pcap.pl # use v5.14; use strict; use warnings; use Net::Pcap; my $err = q{}; my $dev = pcap_lookupdev(\$err); # find a device say $dev; my $pcap = pcap_open_live( $dev, 1024, 1, 0, \$err ); pcap_loop($pcap, 3, \&process_packet, "just for the demo"); pcap_close($pcap); sub process_packet { my ( $user_data, $header, $packet ) = @_; printf "%d/%d %d\n",$header->{caplen}, $header->{len}, length($packet); }
앞의 예제는 패킷을 3번 캡쳐한 후 캡쳐한 자료의 길이, 헤더에 기록된 길이, 그리고 캡쳐된 데이터의 실제 길이를 출력하는 예제입니다. 실행해 볼까요?
$ ./pcap.pl eth0 66/66 66 1024/1514 1024 60/60 60 $
너무 많은 자료는 사실 없는 것이나 마찬가지입니다. 원하는 패킷 정보만 추려내려면 필터 기능을 이용하면 됩니다.
#!/usr/bin/env perl # # FILE: pcap.pl # use v5.14; use strict; use warnings; use Net::Pcap; my $err = q{}; my $dev = pcap_lookupdev(\$err); # find a device say $dev; my $pcap = pcap_open_live( $dev, 1024, 1, 0, \$err ); my $filter; my $filter_str = "host advent.perl.kr"; pcap_compile( $pcap, \$filter, $filter_str, 1, 0 ); pcap_setfilter( $pcap, $filter ); pcap_loop($pcap, 10, \&process_packet, "just for the demo"); pcap_close($pcap); sub process_packet { my ( $user_data, $header, $packet ) = @_; printf "%d/%d %d\n",$header->{caplen}, $header->{len}, length($packet); }
새로워진 예제의 핵심은 다음 두 줄입니다.
pcap_compile($pcap, \$filter, $filter_str, 1, 0); pcap_setfilter($pcap, $filter);
먼저 필터를 컴파일(pcap_compile
)한 뒤 필터를 적용(pcap_setfilter
)합니다.
예제의 host advent.perl.kr
필터는 advent.perl.kr
호스트와 통신하는 패킷을 캡쳐하라는 의미입니다.
필터를 제대로 사용하려면 필터 문법을 제대로 알아야 합니다. 다음은 유용하게 쓰일만한 필터 몇 가지 입니다.
host advent.perl.kr # advent.perl.kr 과 통신하는 모든 패킷 dst host advent.perl.kr # destination 이 advent.perl.kr 인 패킷 src host advent.perl.kr # source 가 advent.perl.kr 인 패킷 port 80 # port가 80인 패킷 dst port 80 # destination port 가 80인 패킷 src port 80 # source port 가 80인 패킷 len <= 10 # 10 바이트 이하인 패킷 len >= 10 # 10 바이트 이상인 패킷
and
, or
, ()
을 조합하여 다양하게 변형이 가능합니다.
# # ip 패킷이면서 192.168.7.0/24대역과 통신하지 않는 패킷 # ip and not net 192.168.7.0/24 # # snup 게이트웨이를 통과하면서 ftp 포트를 사용하는 패킷 # gateway snup and (port ftp or ftp-data)
좀 더 복잡한 조합으로 사용하는 것 역시 가능합니다.
# # http 접속중 SYN패킷만 모아서 보기 # tcp[tcpflags] = tcp-syn and port http
더 자세한 필터 문법은 tcpdump 공식 문서를 참조하세요.
CPAN의 NetPacket 모듈은 패킷 분석을 손쉽게 할 수 있게 도와줍니다. 패킷 분석 기능을 추가한 코드는 다음과 같습니다.
#!/usr/bin/env perl # # FILE: pcap.pl # use v5.14; use strict; use warnings; use Net::Pcap; use NetPacket::Ethernet qw(:strip); use NetPacket::IP qw(:strip); use NetPacket::TCP; my $err = q{}; my $dev = pcap_lookupdev(\$err); say $dev; $dev = 'wlan0'; my $pcap = pcap_open_live( $dev, 1024, 1, 0, \$err ); my $filter; my $filter_str = "host advent.perl.kr"; pcap_compile( $pcap, \$filter, $filter_str, 1, 0 ); pcap_setfilter( $pcap, $filter ); pcap_loop($pcap, 10, \&process_packet, "just for the demo"); pcap_close($pcap); sub process_packet { my ( $user_data, $header, $packet ) = @_; my $ip_obj = NetPacket::IP->decode( eth_strip($packet) ); my $tcp_obj = NetPacket::TCP->decode( $ip_obj->{data} ); printf( "%s:%d->%s:%d (%d) ", $ip_obj->{src_ip}, $tcp_obj->{src_port}, $ip_obj->{dest_ip}, $tcp_obj->{dest_port}, length( $tcp_obj->{data} ) ); print "FIN " if ( $tcp_obj->{flags} & FIN ); print "SYN " if ( $tcp_obj->{flags} & SYN ); print "RST " if ( $tcp_obj->{flags} & RST ); print "PSH " if ( $tcp_obj->{flags} & PSH ); print "ACK " if ( $tcp_obj->{flags} & ACK ); print "URG " if ( $tcp_obj->{flags} & URG ); print "ECE " if ( $tcp_obj->{flags} & ECE ); print "CWR " if ( $tcp_obj->{flags} & CWR ); print "\n"; if ( length( $tcp_obj->{data} ) > 0 ) { print $tcp_obj->{data}, "\n"; print "============\n"; } }
실행 후 advent.perl.kr
호스트에 웹브라우저나 curl
, wget
등으로 접속하면 다음과 같은 결과를 화면에 출력합니다.
$ perl pcap.pl eth0 221.143.48.32:80->192.168.25.37:45902 (0) SYN ACK 192.168.25.37:45902->221.143.48.32:80 (0) ACK 192.168.25.37:45902->221.143.48.32:80 (685) PSH ACK GET /2013/2013-12-04.html HTTP/1.1 Host: advent.perl.kr User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:25.0) Gecko/20100101 Firefox/25.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Referer: http://advent.perl.kr/2013/ Cookie: __utma=97638351.475138252.1385891323.1386204394.1386208284.13; __utmz=97638351.1385891323.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); _fjtads1=443; _fjtad6=3:4T2T5T6T1T3; _fjpvnum1=4; _fjpermvid1=1386195821039-8972276113869514; __utmc=97638351; __utmb=97638351.1.10.1386208284 Connection: keep-alive If-Modified-Since: Tue, 03 Dec 2013 20:57:50 GMT ============ 221.143.48.32:80->192.168.25.37:45902 (0) ACK 221.143.48.32:80->192.168.25.37:45902 (157) PSH ACK HTTP/1.1 304 Not Modified Server: nginx/1.2.4 Date: Thu, 05 Dec 2013 02:11:56 GMT Connection: keep-alive Last-Modified: Tue, 03 Dec 2013 20:57:50 GMT ============ 192.168.25.37:45902->221.143.48.32:80 (0) ACK 192.168.25.37:45905->221.143.48.32:80 (0) SYN 192.168.25.37:45906->221.143.48.32:80 (0) SYN 192.168.25.37:45907->221.143.48.32:80 (0) SYN 192.168.25.37:45908->221.143.48.32:80 (0) SYN
대부분의 패킷 캡쳐와 관련한 기능은 궁극의 pcap 라이브러리가 다 처리합니다.
펄에서는 이 pcap 라이브러리를 바인딩한 Net::Pcap 모듈이
CPAN을 통해 배포되고 있어 간단하게 pcap을 사용할 수 있습니다.
성능 역시 pcap 라이브러리에 의존하기 때문에 C로 작성하는 것과 비교해 별반 차이가 없죠.
아마 여러분에게 닥친 상황이 복잡하면 복잡할수록 여러분은 다른 패킷 캡쳐 도구보다 Net::Pcap
을 더 선호하게 될 겁니다. :-)
Artwork by
@namanvara,
Hyungsuk Hong
& Inkyung Park.
Designed by
Hojung Youn
& Keedi Kim.
Articles by
Seoul Perl Mongers.
Edited by
Keedi Kim.
Hosting generously sponsored by
Yuni Kim.
Sponsored by
SILEX.
.-''' __ __ / \/ \/ \ =-_- | \. -____- / \ // /|| '' //| //|| == = == ==